Python语法进阶(1)- 进程与线程编程

1.进程与多进程

1.1.什么是进程

  • 进程就是程序执行的载体
  • 什么叫多任务?
    • 多任务就是操作系统可以同时运行多个任务。比如你一边在用浏览器学习,还一边在听音乐,,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
  • 什么是进程?
    • 对于操作系统来说,一个任务就是一个进程,比如打开一个浏览器就是启动一个浏览器进程,打开一个word就启动了一个word进程,打开两个记事本就启动了两个记事本进程。
  • 怎样的任务算一个进程?
    • 当一个任务被开启后,操作系统会分配它所需的系统资源,包括内存,I/O和CPU等,如果系统资源不够,则会出现系统崩溃,这样的任务可称为进程。
  • python中如何创建进程?
    • 使用模块:multiprocessing
    • 创建方法: multiprocessing.Process(…)


1.2.进程在生活中的应用

  • 我们打开的每个软件、游戏、执行的每一个python脚本都是启动一个进程
  • 软件(游戏,脚本)==进程

1.3.进程的口粮

每一个进程像人一样需要吃饭,他的粮食就是:cpu和内存


1.4.多进程

可以启动多个进程,他们之间互不干扰,执行自己的业务逻辑


1.5.多进程的执行方式

 

2.线程与多线程

2.1.什么是线程

  • 先有进程再有线程,进程吸收足够的资源(CPU、内存)然后交给线程,线程是真正执行逻辑的角色。
  • 线程是操作系统最小的执行单元,进程至少由一个线程组成。如何调度进程和线程,完全有操作系统决定,程序自己不能决定什么时候执行,执行多长时间。有些进程还不止同时干一件事,比如微信,它可以同时进行语音、发文字、浏览信息等事情。
  • 怎样的任务算一个线程?
    • 进程被运行后算一个线程,进程是不运行的,线程才会运行,而一个进程有多个线程就涉及到进程有多少可以被cpu单独调用的模块,这个调用的模块可以通过手动创建线程来建立。
  • 在python中如何创建线程?
    • 使用模块:threading
    • 创建方法:threading.Thread(…)


2.2.线程与进程的关系

进程提供线程执行程序的前置要求,线程在重组的资源配备下,去执行程序


2.3.多线程

  • 举例说明:开启一个浏览器进程后,从浏览器(主线程)中创建出多个线程来开启多个页面即一个浏览器打开多个tab页,这些tab页就是浏览器进程的多线程
  • 初级认知:多线程会比多进程更加节省资源

2.4.多线程的执行方式

  • 并行:在多个cpu内核上同时执行多个进程
  • 并发:在多个cpu时间片上同时执行多个线程
  • 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行,进程之间不能共享内存,但线程之间可以共享内存

 

3.多进程的创建

3.1.进程的创建模块-multiprocessing

 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 def work_a():
 6     for i in range(3):
 7         print(i,"a",os.getpid())    #os.getpid()获取进程id
 8         time.sleep(1)
 9 
10 def work_b():
11     for i in range(3):
12         print(i,"b",os.getpid())
13         time.sleep(1)
14 
15 if __name__=='__main__':
16     start=time.time()
17     work_a()
18     work_b()
19     end=time.time()-start
20     print(end)
21     print('parent pid is %s' % os.getpid())
22 '''
23 0 a 36436
24 1 a 36436
25 2 a 36436
26 0 b 36436
27 1 b 36436
28 2 b 36436
29 6.002437114715576
30 parent pid is 36436 所有的进程id跟主进程一致,都是36436
31 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #创建进程
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()获取进程id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     a_p=multiprocessing.Process(target=work_a) #将work_a()方法单独作为一个进程执行,由于work_a()方法没有参数,因此args为None,也可以不填
22     a_p.start() #启动work_a()进程
23     work_b()
24     end=time.time()-start
25     print(end)
26     print('parent pid is %s' % os.getpid())
27 '''
28 0 b 35196
29 0 a 24640
30 1 b 35196
31 1 a 24640
32 2 b 35196
33 2 a 24640
34 3.0127575397491455
35 parent pid is 35196
36 work_a()单独用了一个进程运行,work_b()依旧与主线程用的同一个进程
37 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #多个进程执行
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()获取进程id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主进程
21     a_p=multiprocessing.Process(target=work_a)  #子进程1
22     b_p=multiprocessing.Process(target=work_b)  #子进程2
23     end=time.time()-start   #主进程
24     print(end)  #主进程
25     print('parent pid is %s' % os.getpid()) #主进程
26     for i in (a_p,b_p):
27         i.start()
28 '''
29 0.0
30 parent pid is 19692
31 0 a 35408
32 0 b 34000
33 1 a 35408
34 1 b 34000
35 2 a 35408
36 2 b 34000
37 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #join方法使用
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()获取进程id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主进程
21     a_p=multiprocessing.Process(target=work_a)  #子进程1
22     b_p=multiprocessing.Process(target=work_b)  #子进程2
23     for i in (a_p,b_p):
24         i.start()
25         i.join()
26     end=time.time()-start   #主进程
27     print(end)  #主进程
28     print('parent pid is %s' % os.getpid()) #主进程
29 '''
30 0 a 35816
31 1 a 35816
32 2 a 35816
33 0 b 35172
34 1 b 35172
35 2 b 35172
36 6.118025302886963
37 parent pid is 14624
38 '''
 1 # coding:utf-8
 2 
 3 import time
 4 import os
 5 import multiprocessing
 6 
 7 #kill方法使用
 8 
 9 def work_a():
10     for i in range(3):
11         print(i,"a",os.getpid())    #os.getpid()获取进程id
12         time.sleep(1)
13 
14 def work_b():
15     for i in range(3):
16         print(i,"b",os.getpid())
17         time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()   #主进程
21     a_p=multiprocessing.Process(target=work_a)  #子进程1
22     b_p=multiprocessing.Process(target=work_b)  #子进程2
23     for i in (a_p,b_p):
24         i.start()
25         if i==b_p:
26             i.kill()
27     end=time.time()-start   #主进程
28     print(end)  #主进程
29     print('parent pid is %s' % os.getpid()) #主进程
30 '''
31 0.016000986099243164
32 parent pid is 35268
33 0 a 34584
34 1 a 34584
35 2 a 34584
36 '''

3.2.进程的问题

  • 通过进程模块执行的函数无法获取返回值 

  • 多个进程同时修改文件,可能会出现错误

  • 进程太多会导致资源不足


3.3.多进程总结

一、当多个进程运行时,可能会出现的问题及解决方案

  • 通过进程模块执行的函数无法获取返回值–进程间如何通信:通过队列
  • 多个进程同时修改文件可能会出现错误–进程间如何避免资源抢占:创建进程锁
  • 进程数量太多可能会造成资源不足 甚至死机等情况–如何避免创建进程数量过多:创建进程池

二、什么是队列

队列是一种数据存储结构,它的数据存储特点类似于排队,先进入队列的会先出来,后进入队列的 后出来,因此它的数据只要通过put()放入,get()取出即可,不需要安排取哪些数据进程的数据可放 入队列,哪些进程需要,从队列中通过取出,即可使用。

三、如何创建队列

  • 使用的模块:  queue
  • 创建的方法:  queue. Queue(…)

 

4.进程池与进程锁

4.1.什么是进程池


4.2.进程池的创建

进程池的join函数一般伴随着close函数

 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 def work(count):
 8     print(count,os.getpid())
 9     time.sleep(5)
10 
11 
12 if __name__=='__main__':
13     pool=multiprocessing.Pool(5)    #创建进程池,里面有5个进程
14     for i in range(20):
15         pool.apply_async(func=work,args=(i,))   #进程池和进程对象process创建对象的时候里面的参数是元组类型的,单个参数时,结尾使用,逗号标明是元组类型
16 
17     #进程池属于主进程里面的子进程,主进程结束,pycharm就退出了
18     #因此需要使用等待或者pool.close等方法
19     pool.close()
20     pool.join()
21     #为什么是先关闭进程池再等待呢?
22     #pool.close()是关闭进程池,使其不在接受新的任务。防止任何更多的任务被提交到池中。 一旦完成所有任务,工作进程将退出。需要在join之前调用;
23     #可以这样理解,这里的进程池就相当于一辆大巴车,而close相当于大巴车不准上客,join大巴车上的乘客(子进程)都下车之后,大巴车就停运了
24 '''
25 0 10092
26 1 19704
27 2 36164
28 3 37212
29 4 34628
30 5 10092
31 6 19704
32 7 36164
33 8 37212
34 9 34628
35 10 10092
36 11 19704
37 12 36164
38 13 37212
39 14 34628
40 15 10092
41 16 19704
42 17 36164
43 18 37212
44 19 34628
45 '''
 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 #通过进程池可以获取返回值
 8 def work(count):
 9     print(count,os.getpid())
10     time.sleep(5)
11     return 'result is %s ,pid is %s' % (count,os.getpid())
12 
13 
14 if __name__=='__main__':
15     pool=multiprocessing.Pool(5)    #创建进程池,里面有5个进程
16     results=[]
17     for i in range(20):
18         result=pool.apply_async(func=work,args=(i,))   #进程池和进程对象process创建对象的时候里面的参数是元组类型的,单个参数时,结尾使用,逗号标明是元组类型
19         results.append(result)
20 
21     for res in results:
22         print(res.get())
23 
24 '''
25 0 10092
26 1 19704
27 2 36164
28 3 37212
29 4 34628
30 5 10092
31 6 19704
32 7 36164
33 8 37212
34 9 34628
35 10 10092
36 11 19704
37 12 36164
38 13 37212
39 14 34628
40 15 10092
41 16 19704
42 17 36164
43 18 37212
44 19 34628
45 '''

4.3.进程锁


4.4.进程锁的用法

 1 # coding:utf-8
 2 
 3 import os
 4 import time
 5 import multiprocessing
 6 
 7 #进程锁
 8 def work(count,lock):
 9     lock.acquire()
10     print(count,os.getpid())
11     time.sleep(1)
12     lock.release()
13     return 'result is %s ,pid is %s' % (count,os.getpid())
14 
15 
16 if __name__=='__main__':
17     pool=multiprocessing.Pool(5)
18     manger=multiprocessing.Manager()
19     lock = manger.Lock()
20     results=[]
21     for i in range(20):
22         result=pool.apply_async(func=work,args=(i,lock))
23 
24     pool.close()
25     pool.join()

 

5.进程间的通信

5.1.什么是进程的通信

使用队列进行进程之间的通信


5.2.进程通信的方法

 1 # coding:utf-8
 2 import json
 3 import multiprocessing
 4 
 5 class Work(object):
 6     def __init__(self,q):
 7         self.q=q
 8 
 9     def send(self,message):
10         if not isinstance(message,str):
11             message=json.dumps(message)
12         self.q.put(message)
13 
14     def receive(self):
15         while 1:
16             result=self.q.get()
17             try:
18                 res=json.loads(result)
19             except:
20                 res = result
21             print('recv is %s' % res)
22 
23 if __name__=='__main__':
24     q=multiprocessing.Queue()
25     work=Work(q)
26     send=multiprocessing.Process(target=work.send,args=({'name':'zhangsan'},))
27     recv=multiprocessing.Process(target=work.receive)
28     send.start()
29     recv.start()
30 
31     send.join()
32     recv.terminate()    #强行终止
 1 # coding:utf-8
 2 import json
 3 import multiprocessing
 4 import time
 5 
 6 
 7 class Work(object):
 8     def __init__(self,q):
 9         self.q=q
10 
11     def send(self,message):
12         if not isinstance(message,str):
13             message=json.dumps(message)
14         self.q.put(message)
15 
16     def send_all(self):
17         for i in range(10):
18             self.q.put(i)
19             time.sleep(1)
20 
21     def receive(self):
22         while 1:
23             result=self.q.get()
24             try:
25                 res=json.loads(result)
26             except:
27                 res = result
28             print('recv is %s' % res)
29 
30 if __name__=='__main__':
31     q=multiprocessing.Queue()
32     work=Work(q)
33     send=multiprocessing.Process(target=work.send,args=({'name':'zhangsan'},))
34     recv=multiprocessing.Process(target=work.receive)
35     send_all_p=multiprocessing.Process(target=work.send_all)
36     send.start()
37     recv.start()
38     send_all_p.start()
39 
40     send_all_p.join()
41     recv.terminate()    #强行终止

 

6.线程的创建

6.1.线程的创建

 1 # coding:utf-8
 2 
 3 import random
 4 import time
 5 
 6 lists=['python','django','tornado','flask','bs5','requests','uvloop']
 7 
 8 new_lists=[]
 9 
10 def work():
11     if len(lists) == 0:
12         return
13     data=random.choice(lists)
14     lists.remove(data)
15     new_data='%s_new' % data
16     new_lists.append(new_data)
17     time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     for i in range(len(lists)):
22         work()
23     print('old list',lists)
24     print('new list',new_lists)
25     print('time is %s' % (time.time()-start))
26 '''
27 old list []
28 new list ['uvloop_new', 'requests_new', 'django_new', 'bs5_new', 'flask_new', 'python_new', 'tornado_new']
29 time is 7.003076553344727
30 '''
 1 # coding:utf-8
 2 
 3 import random
 4 import time
 5 import threading
 6 lists=['python','django','tornado','flask','bs5','requests','uvloop']
 7 
 8 new_lists=[]
 9 
10 def work():
11     if len(lists) == 0:
12         return
13     data=random.choice(lists)
14     lists.remove(data)
15     new_data='%s_new' % data
16     new_lists.append(new_data)
17     time.sleep(1)
18 
19 if __name__=='__main__':
20     start=time.time()
21     t_list=[]
22     for i in range(len(lists)):
23         t=threading.Thread(target=work)
24         t_list.append(t)
25         t.start()
26 
27     for t in t_list:
28         t.join()
29     print('old list',lists)
30     print('new list',new_lists)
31     print('time is %s' % (time.time()-start))
32 '''
33 old list []
34 new list ['requests_new', 'tornado_new', 'flask_new', 'bs5_new', 'python_new', 'django_new', 'uvloop_new']
35 time is 1.0019862651824951
36 '''

6.2.线程的问题


6.3.线程创建总结

一、当多个线程运行时,可能会出现的问题及解决方案:

  • 通过线程执行的函数无法获取返回值——线程间如何通信:通过队列
  • 多个线程同时修改文件可能造成数据错错乱——线程间如何避免资源抢占:创建线程锁
  • 线程数量太多可能会造成资源不足,甚至死机等情况——如何避免创建线程数量过多:创建线程池

二、通过队列通信来解决

 

三、创建线程锁:

在线程代码中需要加上锁的地方写上加锁代码,要释放锁的地方写解锁代码即可

  • 使用模块:threading
  • 加锁:threading.Lock().acquire()
  • 解锁:threading.Lock().release()

四、创建线程池:

首先写出创建线程池的方法,之后往线程池中放入线程即可

  • 使用模块:concurrent.futures
  • 创建方法:concurrent.futures.ThreadPoolExecutor()

 

7.线程池

线程池的创建与使用方法,线程池不像进程池需要close(),而后join()

 1 # coding:utf-8
 2 import time
 3 from concurrent.futures import ThreadPoolExecutor
 4 
 5 def work(i):
 6     print(i)
 7     time.sleep(1)
 8 
 9 if __name__=="__main__":
10     t=ThreadPoolExecutor(2)
11     for i in range(10):
12         t.submit(work,i)
13         
14 '''
15 0
16 1
17 23
18 
19 4
20 5
21 6
22 7
23 8
24 9
25 '''
 1 # coding:utf-8
 2 import threading
 3 import time
 4 from concurrent.futures import ThreadPoolExecutor
 5 
 6 #线程锁和进程锁的区别:进程锁需要将锁传到方法中,而线程锁只需要定义一个全局锁,直接调用即可
 7 lock=threading.Lock()
 8 
 9 def work(i):
10     lock.acquire()
11     print(i)
12     time.sleep(1)
13     lock.release()
14 
15 if __name__=="__main__":
16     t=ThreadPoolExecutor(2)
17     for i in range(10):
18         t.submit(work,i)
19 
20 '''
21 0
22 1
23 2
24 3
25 4
26 5
27 6
28 7
29 8
30 9
31 '''
 1 # coding:utf-8
 2 import os
 3 import threading
 4 import time
 5 from concurrent.futures import ThreadPoolExecutor
 6 
 7 #线程池和进程池一样可以获得线程的返回值,多线程的os.getpid和当前进程的一致,用的同一个,因为线程池就是在当前进程下执行的
 8 
 9 def work(i):
10     time.sleep(1)
11     return 'result %s' % i
12 
13 if __name__=="__main__":
14     print(os.getpid())      #57764
15     t=ThreadPoolExecutor(2)
16     list_1=[]
17     for i in range(10):
18         result=t.submit(work,i)
19         list_1.append(result)
20     print(list_1,type(list_1))   #[<Future at 0x2bfbe0bc748 state=running>, <Future at 0x2bfbe2fbd08 state=running>, <Future at 0x2bfbe301808 state=pending>, <Future at 0x2bfbe301908 state=pending>, <Future at 0x2bfbe3019c8 state=pending>, <Future at 0x2bfbe301b48 state=pending>, <Future at 0x2bfbe301cc8 state=pending>, <Future at 0x2bfbe301e88 state=pending>, <Future at 0x2bfbe30c048 state=pending>, <Future at 0x2bfbe301b08 state=pending>] <class 'list'>
21 
22     for i in list_1:
23         print(i.result())
24 '''
25 result 0
26 result 1
27 result 2
28 result 3
29 result 4
30 result 5
31 result 6
32 result 7
33 result 8
34 result 9
35 '''

 

8.全局锁

 一、什么是全局锁:

GIL是全局解释器锁,这个GIL并不是python的特性,他是在Cpython解释器里引入的一个概念,而 在其他的语言编写的解释器里就没有这个GIL 

二、全局锁是主要的作用 :

因为多线程的编程方式,使得线程之间数据的一致性和状态同步难以把控,为了解决数据不能同步 的问题,设计了GIL全局解释器锁。

三、全局锁是如何发挥作用的 :

在Cpython解释器中,当python代码有一个线程开始访问解释器的时候,GIL会把这个线程给锁上, 此时此刻其他的线程只能干等着,无法对解释器的资源进行访问,需要等这个线程分配的时间到 了,这个线程把GIL释放掉,另外的线程才开始跑起来,其实这无疑也是一个单线程。这类似于给 线程加锁 threading.Lock().acquire() ,解锁 threading.Lock().release() 一样。

python有全局锁gil,多线程只能在单一cpu工作。可以用多进程+多线程配合使用

 

 

 

9.异步

9.1.什么是异步与异步的好处


9.2.异步与多线程多进程


9.3.async、await与asyncio模块的使用

 1 # coding:utf-8
 2 import random
 3 import time
 4 
 5 
 6 def a():
 7     for i in range(10):
 8         print(i,'a')
 9         time.sleep(random.random()*2)
10     return 'a function'
11 
12 def b():
13     for i in range(10):
14         print(i,'b')
15         time.sleep(random.random()*2)
16     return 'b function'
17 
18 
19 if __name__=='__main__':
20     start=time.time()
21     a()
22     b()
23     print(time.time()-start)
24 '''
25 0 a
26 1 a
27 2 a
28 3 a
29 4 a
30 5 a
31 6 a
32 7 a
33 8 a
34 9 a
35 0 b
36 1 b
37 2 b
38 3 b
39 4 b
40 5 b
41 6 b
42 7 b
43 8 b
44 9 b
45 19.68557095527649
46 '''
 1 # coding:utf-8
 2 import asyncio
 3 import random
 4 import time
 5 
 6 
 7 async def a():
 8     for i in range(10):
 9         print(i,'a')
10         await asyncio.sleep(random.random()*2)
11     return 'a function'
12 
13 async def b():
14     for i in range(10):
15         print(i,'b')
16         await asyncio.sleep(random.random()*2)
17     return 'b function'
18 
19 async def main():
20     result=await asyncio.gather(
21         a(),
22         b()
23     )
24     print(result)
25 
26 if __name__=='__main__':
27     start=time.time()
28     asyncio.run(main())
29     print(time.time()-start)
30 '''
31 0 a
32 0 b
33 1 b
34 2 b
35 1 a
36 3 b
37 2 a
38 3 a
39 4 b
40 4 a
41 5 a
42 5 b
43 6 a
44 7 a
45 6 b
46 8 a
47 7 b
48 9 a
49 8 b
50 9 b
51 ['a function', 'b function']
52 10.612190246582031
53 '''
 1 # coding:utf-8
 2 import asyncio
 3 import random
 4 import time
 5 
 6 
 7 async def a():
 8     for i in range(10):
 9         print(i,'a')
10         await asyncio.sleep(random.random()*2)
11     return 'a function'
12 
13 async def b():
14     for i in range(10):
15         print(i,'b')
16         await asyncio.sleep(random.random()*2)
17     return 'b function'
18 
19 async def main():
20     result=await asyncio.gather(
21         a(),
22         b()
23     )
24     print(result)
25 
26 if __name__=='__main__':
27     start=time.time()
28     # asyncio.run(main())
29     asyncio.run(b())
30     print(time.time()-start)
31 '''
32 0 b
33 1 b
34 2 b
35 3 b
36 4 b
37 5 b
38 6 b
39 7 b
40 8 b
41 9 b
42 6.932542085647583
43 '''
 1 # coding:utf-8
 2 import asyncio
 3 import os
 4 import random
 5 import time
 6 
 7 
 8 async def a():
 9     for i in range(10):
10         print(i,'a',os.getpid())
11         await asyncio.sleep(random.random()*2)
12     return 'a function'
13 
14 async def b():
15     for i in range(10):
16         print(i,'b',os.getpid())
17         await asyncio.sleep(random.random()*2)
18     return 'b function'
19 
20 async def main():
21     result=await asyncio.gather(
22         a(),
23         b()
24     )
25     print(result)
26 
27 if __name__=='__main__':
28     start=time.time()
29     asyncio.run(main())
30     print(time.time()-start)
31     print('parent is %s' % os.getpid())#异步相当于线程的存在,用的都是同一个进程id
32 '''
33 0 a 96060
34 0 b 96060
35 1 b 96060
36 2 b 96060
37 1 a 96060
38 3 b 96060
39 4 b 96060
40 2 a 96060
41 5 b 96060
42 6 b 96060
43 3 a 96060
44 4 a 96060
45 7 b 96060
46 8 b 96060
47 5 a 96060
48 6 a 96060
49 7 a 96060
50 9 b 96060
51 8 a 96060
52 9 a 96060
53 ['a function', 'b function']
54 8.424062490463257
55 parent is 96060
56 
57 Process finished with exit code 0
58 
59 '''

9.4.gevent异步模块的使用

 1 # coding:utf-8
 2 import os
 3 import random
 4 import time
 5 import gevent
 6 
 7 def gevent_a():
 8     for i in range(5):
 9         print(i,'a',os.getpid())
10         gevent.sleep(random.random()*2)
11     return 'a function'
12 
13 def gevent_b():
14     for i in range(5):
15         print(i,'b',os.getpid())
16         gevent.sleep(random.random()*2)
17     return 'b function'
18 
19 if __name__=='__main__':
20     start=time.time()
21     g_a=gevent.spawn(gevent_a)
22     g_a.run()
23     print(time.time()-start)
24     print('parent is %s' % os.getpid())#异步相当于线程的存在,用的都是同一个进程id
25 '''
26 0 a 99784
27 1 a 99784
28 2 a 99784
29 3 a 99784
30 4 a 99784
31 6.335636615753174
32 parent is 99784
33 '''
 1 # coding:utf-8
 2 import os
 3 import random
 4 import time
 5 import gevent
 6 
 7 def gevent_a():
 8     for i in range(5):
 9         print(i,'a',os.getpid())
10         gevent.sleep(random.random()*2)
11     return 'a function'
12 
13 def gevent_b():
14     for i in range(5):
15         print(i,'b',os.getpid())
16         gevent.sleep(random.random()*2)
17     return 'b function'
18 
19 if __name__=='__main__':
20     start=time.time()
21     g_a=gevent.spawn(gevent_a)
22     g_b=gevent.spawn(gevent_b)
23     gevent_list=[g_a,g_b]
24     result=gevent.joinall(gevent_list)
25     print(result)
26     print(result[0].value)
27     print(time.time()-start)
28     print('parent is %s' % os.getpid())#异步相当于线程的存在,用的都是同一个进程id
29 '''
30 0 a 99840
31 0 b 99840
32 1 b 99840
33 2 b 99840
34 1 a 99840
35 3 b 99840
36 2 a 99840
37 4 b 99840
38 3 a 99840
39 4 a 99840
40 [<Greenlet at 0x20ec1afcee8: _run>, <Greenlet at 0x20ec1afcca8: _run>]
41 b function
42 8.296916961669922
43 parent is 99840
44 '''

9.5.异步总结

异步相对同步而言,异步意味着无序,而同步意味着有序。正因为异步的无序,使得各个程序间的协调成为一大难题。由此,异步编程应运而生。它是以进程,线程,协程,函数/方法作为执行程序的基本单位,结合回调,事件循环,信号量等机制,以提高程序整体执行效率和并发能力的一种编程方式。

在python中,如何实现异步?

方法一:

  • 如何定义一个异步: async
  • 如何在一个异步程序中,调用另外一个异步,使用关键字 await
  • 非异步程序,如何调用异步函数: asyncio

方法二:通过异步包gevent

  • 如何创建协程即异步对象:spwan 函数
  • 如何批量处理协程对象:joinall

 

10.多线程多进程面试常问知识点

谈谈你对进程,线程,协程的理解进程:

  • 一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。进程的状态有:新建态,就绪态,运行态,阻塞态,终止态
  • 线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线 程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
  • 协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和 栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

什么是多线程竞争

  • 线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态,即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 那么怎么解决多线程竞争问题?—锁
  • 锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资 源竞争下的原子操作问题。
  • 锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下 降了
  • 锁的致命问题: 死锁

解释一下什么是锁,什么是死锁

  • 锁:当一个线程去访问某一个资源的时候,对这个资源上锁,其他线程就无法对这个资源进行访问,等这个线程处理完成了,对锁进行释放,其他线程再访问这个资源
  • 死锁:当因为某些原因,比如锁无法释放,循环等待资源释放的时候,需要该资源的线程一直无法执行,就进入死锁状态

什么是线程安全,什么是互斥锁

  • 互斥锁是一种独占锁,同一时刻只有一个线程可以访问共享的数据。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  • 同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。

python中进程和线程的使用场景

  • 多进程适合在CPU密集操作,业务处理(cpu操作指令比较多,如位多的的浮点运算)。
  • 多线程适合在IO密性型操作(读写数据操作比多的的,比如爬虫)

 

11.拓展

Python进程与线程的概念及区别 :

  • 进程可以被称为执行的程序,一个进程拥有完整的数据空间和代码空间,每一个进程的地址空 间都是独立的,进程之间不能共享数据。 
  • 线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本 单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一 个进程的其他的线程共享进程所拥有的全部资源。
  • 通俗的讲一条流水线的执行过程是一个线程,一条流水线必须属于一个车间,一个车间的运行 过程就是一个进程。

 二者之间的关系:

 一个程序至少有一个进程,一个进程至少有一个线程 

区别:

  • 进程有独立的地址空间,多进程较稳定,因为其中一个出现状况不影响另外一个;同一个进程的多个线程,共用地址空间,多线程相比于多进程,稳定性要差,因为一个线程出现问题会严 重影响其他线程。 
  • 进程之间需要共享数据,要利用进程间通讯;同一个进程中的线程不需要。 
  • 进程只是资源分配的最小单位;线程是执行的最小单位,也就是说实际执行的是线程。 

CPU密集型和IO密集型:

  • CPU密集型代码(各种循环处理、计数等等) 
  • IO密集型代码(文件处理、网络爬虫等) 

线程与进程谁更快: 

因为python锁的问题,线程进行锁竞争、切换线程,会消耗资源。在CPU密集型任务下,多进程更快或者说效果更好;而IO密集型,多线程能有效提高效率。