并发编程 之 进程 之 进程间通信(Queue, Pipe, 生产者消费者模型), 数据共享(Manager), 进程池 (四)
一, 进程间通信 --- 队列, 管道.
ps: IPC == 进程之间的通信
1, 队列:
1, 正常队列: from queue import Queue
queue.Queue 学名(FIFO) 先进先出 -- 维护秩序的时候用的比较多,
q = Queue() 实例化队列对象, 可以传一个最大元素个数
put() 放入 如果超出该队列最大元素个数 则 堵塞
put_nowait() 放入, 如果超出,会报错.
get() 拿出 等同于list.pop, 如果没有值 会一直堵塞.
get_nowait() 拿出, 只不过当没有值的时候 会报错.
qsize() 查询队列中元素的个数.
full() 判断队列是否满了 返回布尔值
empty() 判断段队列是否为空 返回布尔值
ps: 栈 先进后出 -- 常用于算法.
2, 进程中的队列: from multiprocessing import Queue
方法同上, 唯一不同的是 full() empty()在进程中的值不够准确
1 from multiprocessing import Queue, Process 2 import time 3 4 def get_q(q): 5 print(q.get()) 6 7 def put_q(q): 8 time.sleep(2) 9 q.put(123) 10 11 if __name__ == '__main__': 12 q = Queue() 13 p1 = Process(target=get_q,args=(q,)).start() 14 p2 = Process(target=put_q,args=(q,)).start()
3, 生产者消费者模型:解决创造(生产)数据 和处理(消费)数据的效率不平衡的问题
把创造数据 和 处理 数据放在不同的进程中, 根据他们的效率来调整进程的个数
1, 生产数据快, 消费数据慢, 内存空间的浪费
2, 消费数据快, 生产数据慢, 效率低下.
ps: consumer 消费者 producer 生产者.
1 # 此代码会导致 程序一直处于get() 的堵塞中. 2 # 意味着 消费者没有的进程还没结束. 3 # 不要着急, 后面会有解决此问题的方法 4 5 import time 6 import random 7 from multiprocessing import Process,Queue 8 9 def consumer(q,name): 10 while True: 11 food = q.get() 12 if food == 'stop':break 13 print('%s 吃了 %s'%(name,food)) 14 time.sleep(random.random()) 15 16 def producer(q,name,food,n=10): 17 for i in range(n): 18 time.sleep(random.random()) 19 fd = food+str(i) 20 print('%s 生产了 %s'%(name,fd)) 21 q.put(fd) 22 23 if __name__ == '__main__': 24 q = Queue(10) 25 c1 = Process(target=consumer,args=(q,'alex')) 26 c1.start() 27 c2 = Process(target=consumer, args=(q, 'alex')) 28 c2.start() 29 p1 = Process(target=producer,args=(q,'太白','泔水')) 30 p1.start() 31 p2 = Process(target=producer, args=(q, 'egon', '鱼刺')) 32 p2.start()
3, 让消费者自动停下来:
1, 在所有的生产者生产结束后, 向队列中放一个结束符
2, 有几个consumer(消费者) 就向队列中放几个结束符
3, 在消费者消费的过程中, 接收到结束符, 就结束消费进程
1 import time 2 import random 3 from multiprocessing import Process,Queue 4 5 def consumer(q,name): 6 while True: 7 food = q.get() 8 if food == 'stop':break 9 print('%s 吃了 %s'%(name,food)) 10 time.sleep(random.random()) 11 12 def producer(q,name,food,n=10): 13 for i in range(n): 14 time.sleep(random.random()) 15 fd = food+str(i) 16 print('%s 生产了 %s'%(name,fd)) 17 q.put(fd) 18 19 if __name__ == '__main__': 20 q = Queue(10) 21 c1 = Process(target=consumer,args=(q,'alex')) 22 c1.start() 23 c2 = Process(target=consumer, args=(q, 'alex')) 24 c2.start() 25 p1 = Process(target=producer,args=(q,'太白','泔水')) 26 p1.start() 27 p2 = Process(target=producer, args=(q, 'egon', '鱼刺')) 28 p2.start() 29 p1.join() # 让主进程 join住,去等待第一个生产者生产完毕 30 p2.join() # 让主进程 join住,去等待第二个生产者生产完毕 31 q.put('stop') # 然后在 queue中 put一个停止符, 有几个消费者就 put几个停止符 32 q.put('stop')
4, 此方法 看起来比较low 不够自动化, 还得让 程序员先去确认有几个 消费者, 要知道消费者 本身就是一个不确定因素, 所以大神们 写出来一个模块叫做 JoinableQueue
4, JoinableQueue 比Queue 多了两个方法,并且内部多了一个计数器.: 此模块用来解决消费者被堵塞的问题
ps: 这里的消费者要作为主进程的一个守护进程存在.
1, join() 堵塞等消费者消费完毕之后结束堵塞状态
2, task_done() 让队列中的计数器-1
1 import time 2 import random 3 from multiprocessing import JoinableQueue,Process 4 # join 阻塞 5 def consumer(q,name): 6 while True: 7 food = q.get() 8 print('%s 吃了 %s'%(name,food)) 9 time.sleep(random.random()) 10 q.task_done() 11 12 def producer(q,name,food,n=10): 13 for i in range(n): 14 time.sleep(random.random()) 15 fd = food+str(i) 16 print('%s 生产了 %s'%(name,fd)) 17 q.put(fd) 18 # q.join() 19 20 if __name__ == '__main__': 21 q = JoinableQueue() 22 c1 = Process(target=consumer,args=(q,'alex')) 23 c1.daemon = True 24 c1.start() 25 c2 = Process(target=consumer, args=(q, 'alex')) 26 c2.daemon = True 27 c2.start() 28 p1 = Process(target=producer,args=(q,'太白','泔水')) 29 p1.start() 30 p2 = Process(target=producer, args=(q, 'egon', '鱼刺')) 31 p2.start() 32 p1.join() 33 p2.join() # 此进程的join 必须存在. 34 q.join() # 此方法可放在 函数中, 也可放在主进程中 35 36 JoinableQueue解决消费者停下的问题
ps: 1, 只有在 multiprocessing 中的队列, 才能帮助你实现PIC
2, 永远不可能出现 数据不安全的情况, 多个进程不会同时get同一个数据
3, 由于先进先出的特点+ 进程通信的功能+ 数据进程安全, 经常用它来完成进程之间的通信
2, 管道: 队列就是基于管道实现的.
队列 数据是安全的.
管道 数据不安全的
队列 = 管道 + 锁
方法:
from multiprocessing import Pipe
1, left,right = Pipe() 此实例化会产生两个端点对象
2, (left/right).recv() 接受另外一端发来的消息
3, (left/right).send() 向另外一端发送消息
ps: 因为 管道是在文件中进行传递消息, 所以 不用要求 必须是bytes类型的数据, 所以 reve/send 两个方法没有太多的要求.
4, (left/right).close() 关闭某一端
注意: 在不同的进程中, 每用 left/right 都会在管道中开一个接口,所以,必须要把没用的接口全部关闭
1 from multiprocessing import Pipe 2 left,right = Pipe() 3 left.send('aaa') 4 print(right.recv()) 5 6 from multiprocessing import Pipe,Process 7 def consumer(left,right): 8 left.close() 9 while True: 10 print(right.recv()) 11 12 if __name__ == '__main__': 13 left,right = Pipe() 14 p = Process(target=consumer,args=(left,right)) 15 p.start() 16 for i in range(10): 17 left.send('hello') 18 19 20 ''' 21 程序会一直停在 right.reve() 22 '''
EOF异常的触发:
1, 在一个进程中,如果不在用 left/right 端口则应该用close()关闭该端口
2, 如果其他端点都被关闭, 只剩下一个端点在 reve()的时候,该端点就能够知道不会再有新的消息传来
3, 此时 recv() 不会在进行堵塞, 则抛出 EOFError异常
4, 注意: close() 并没有将整个管道关闭, 实际,是给操作系统对管道端点的计数处理的功能执行-1的操作
1 from multiprocessing import Pipe 2 left,right = Pipe() 3 left.send('aaa') 4 print(right.recv()) 5 6 from multiprocessing import Pipe,Process 7 def consumer(left,right): 8 left.close() 9 while True: 10 #try: 11 print(right.recv()) 12 #except EOFError: 13 #break 14 15 if __name__ == '__main__': 16 left,right = Pipe() 17 p = Process(target=consumer,args=(left,right)) 18 p.start() 19 right.close() 20 for i in range(10): 21 left.send('hello') 22 left.close() 23 24 ''' 25 此时程序在执行完后得到你想要的结果后 会报错, 26 在 right.reve() 前后加上 try 就可以了. 27 '''
二, 进程之间的数据共享: Manager模块
此模块中包含一些平常常用的数据类型,可以将原本不可以在进程与进程之间共享的数据, 进行共享
因为此共享是基于管道来实现, 所以, 此模块中的数据类型除了队列,是不安全的.
1 进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的 2 虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此 3 4 A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies. 5 6 A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array. 7 8 重要的内容说三遍: 9 ''' 10 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array. 11 12 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array. 13 14 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array. 15 '''
1 from multiprocessing import Manager,Process,Lock 2 def work(d,lock): 3 with lock: #不加锁而操作共享的数据,肯定会出现数据错乱 4 d['count']-=1 5 6 if __name__ == '__main__': 7 lock=Lock() 8 with Manager() as m: 9 dic=m.dict({'count':100}) 10 p_l=[] 11 for i in range(100): 12 p=Process(target=work,args=(dic,lock)) 13 p_l.append(p) 14 p.start() 15 for p in p_l: 16 p.join() 17 print(dic)
三, 进程池: Pool (面向高计算型的场景, 采用多进程.)
进程池出现的原因:
进程不能不限制的开启, 会给os的调度增加负担,且 正真能被同时执行的进程最多也就和 CPU个数相等, 所以此时就出现了进程池的概念, 同时打开和CUP个数相等的进程, 每执行完一个再进来一个进程, 这样限制了同时打开的进程的个数, 有利于os系统的调度, 同时也能节省内存.
apply(func) 将某个函数 执行 同步, 有返回值.
1 import os 2 import time 3 from multiprocessing import Pool 4 # 同步请求的 5 def wahaha(): 6 time.sleep(1) 7 print(os.getpid()) 8 return True 9 10 if __name__ == '__main__': 11 p = Pool(5) # CPU的个数 或者 +1 12 ret_l = [] 13 for i in range(20): 14 ret = p.apply(func = wahaha) # 同步的,不常用 15 print(ret) 16 17 ''' 18 会一个一个打印, getpid 19 达不到想通过进程来提高运行速度的目的. 20 所以一般不用这个同步方法: apply() 21 '''
apply_async() 带着'async'都是异步 返回一个队列对象.
1, 如果是异步的提交任务, 那么任务提交之后进程池和主进程也异步了, 主进程不会自动等待进程池中的任务执行完毕
2, 如果需要主进程等待, 则用 join(), 但是join()是依赖close()
3, 如果函数有返回值,
可以通过ret.get() 来获取返回值.
但如果一边提交一遍获取返回值会让程序变成同步的
所以想要保留异步的效果, 应该将返回对象保存在列表里, 所有任务提交完成之后,再来取结果
这种方式也可以去掉join.
close() 关闭进程池, 让任务不能继续提交.
join() 堵塞等待子进程结束
1 import os 2 import time 3 from multiprocessing import Pool 4 5 # 异步提交,获取返回值,等待所有任务都执行完毕之后再统一获取结果 6 def wahaha(): 7 time.sleep(1) 8 print(os.getpid()) 9 return True 10 11 if __name__ == '__main__': 12 p = Pool(5) # CPU的个数 或者 +1 13 ret_l = [] 14 for i in range(20): 15 ret = p.apply_async(func = wahaha) # async 异步的 16 ret_l.append(ret) 17 p.close() # 关闭 进程池中的进程不工作了 18 # 而是关闭了进程池,让任务不能再继续提交了 19 p.join() # 等待这个池中提交的任务都执行完 20 for ret in ret_l: 21 print(ret.get()) 22 23 ''' 24 此代码会等所有进程执行完后, 打印返回值. 25 进程执行会呈现为 每5个为一组 噗噗噗的出来 26 返回值, 是因为 pool能取到返回值 27 '''
回调函数: -- 在主进程中执行.
在发起任务的时候, 指定callback参数, 在每个进程执行完任务之后, 返回值会直接作为参数传递给callback的函数, 然后执行 callback函数中的代码
1 import os 2 import time 3 import random 4 from multiprocessing import Pool 5 6 # 异步提交,获取返回值,从头到尾一个任务执行完毕之后就可以获取到一个结果 7 def wahaha(num): 8 time.sleep(random.random()) 9 print('pid : ',os.getpid(),num) 10 return num 11 12 def back(arg): 13 print('call_back : ',os.getpid(),arg) 14 15 if __name__ == '__main__': 16 print('主进程',os.getpid()) 17 p = Pool(5) # CPU的个数 或者 +1 18 for i in range(20): 19 ret = p.apply_async(func = wahaha,args=(i,),callback=back) # async 异步的 20 p.close() 21 p.join() 22 23 24 # 回调函数 _ 在主进程中执行 25 # 在发起任务的时候 指定callback参数 26 # 在每个进程执行完apply_async任务之后,返回值会直接作为参数传递给callback的函数,执行callback函数中的代码
四, 进程小结:
0, 概念总结:
1, 进程三状态
2, 同步异步阻塞非阻塞
扩展面试题:
1, 解释异步非阻塞
2, 给一个装饰器加 log
1, 进程是计算机中最小的资源分配单位
2, 进程的创建 Process模块
3, 进程之间的数据隔离 进程和进程之间本就是隔离的
4, 进程之间的异步 进程和主进程之间本身都是异步
5, 进程之间的同步控制 Lock, Semaphore, Event
6, 进程之间的通信 IPC 队列, 管道
7, 数据共享 Manager 模块
8, 进程池. --> 可以获取返回值
1, 同步调用 (用的不多)
apply()
2, 异步调用
apply_async()
get()
close()
join()
3, 回调函数: 在主进程中执行
apply_async(func, callback)