守护进程
关于守护进程需要强调两点
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
from multiprocessing import Process import time def foo(): pass def run(): print('this is 子进程') time.sleep(3) p1 = Process(target=foo,) # AssertionError: daemonic processes are not allowed to have children p1.start() if __name__ == '__main__': p = Process(target=run,) p.daemon = True # 守护进程要主进程执行完毕前开启 p.start() p.join() print('主进程')
互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,
主要参数:
from multiprocessing import Process, Lock #导入锁
mutex.acquire() #加锁
mutex.release() #释放锁
from multiprocessing import Process, Lock import time def run(name, mutex): mutex.acquire() # 加锁 print('this is 进程%s 1' % name) time.sleep(1) print('this is 进程%s 2' % name) time.sleep(1) print('this is 进程%s 3' % name) mutex.release() # 释放锁 if __name__ == '__main__': mutex = Lock() for i in range(3): p = Process(target=run, args=(i, mutex)) p.start() print('主进程,执行结束')
执行结果:
用户 0 查看,剩余票数为:5 用户 1 查看,剩余票数为:5 用户 2 查看,剩余票数为:5 用户 3 查看,剩余票数为:5 用户 4 查看,剩余票数为:5 用户 5 查看,剩余票数为:5 用户 6 查看,剩余票数为:5 用户 7 查看,剩余票数为:5 用户 8 查看,剩余票数为:5 用户 9 查看,剩余票数为:5 用户 0 购票成功 用户 1 购票成功 用户 2 购票成功 用户 3 购票成功 用户 4 购票成功 用户 5 购票失败 用户 6 购票失败 用户 7 购票失败 用户 8 购票失败 用户 9 购票失败
互斥锁与join
使用join可以将并发变成串行,互斥锁的原理也是将并发变成穿行,那我们直接使用join就可以了啊,为何还要互斥锁?
说到这里我赶紧试了一下。
from multiprocessing import Process, Lock import time import json def show(name): dic = json.load(open('db.txt', 'r', encoding='utf-8')) print('%s 查看,剩余票数为:%s ' % (name, dic['count'])) def get(name): time.sleep(0.5) dic = json.load(open('db.txt', 'r', encoding='utf-8')) if dic['count'] != 0: dic['count'] -= 1 time.sleep(0.5) json.dump(dic, open('db.txt', 'w', encoding='utf-8')) print('%s 购票成功' % name) else: print('%s 购票失败' % name) def run(name): show(name) # muext.acquire() get(name) # muext.release() if __name__ == '__main__': # muext = Lock() for i in range(10): p = Process(target=run, args=('用户 %s' % i,)) p.start() p.join()
执行结果:
用户 0 查看,剩余票数为:5 用户 0 购票成功 用户 1 查看,剩余票数为:4 用户 1 购票成功 用户 2 查看,剩余票数为:3 用户 2 购票成功 用户 3 查看,剩余票数为:2 用户 3 购票成功 用户 4 查看,剩余票数为:1 用户 4 购票成功 用户 5 查看,剩余票数为:0 用户 5 购票失败 用户 6 查看,剩余票数为:0 用户 6 购票失败 用户 7 查看,剩余票数为:0 用户 7 购票失败 用户 8 查看,剩余票数为:0 用户 8 购票失败 用户 9 查看,剩余票数为:0 用户 9 购票失败
发现使用join将并发改成穿行,确实能保证数据安全,但问题是连查票操作也变成只能一个一个人去查了,很明显大家查票时应该是并发地去查询而无需考虑数据准确与否,此时join与互斥锁的区别就显而易见了,join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行,比如只让task函数中的get任务串行。
互斥锁小结
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行地修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1、效率低(共享数据基于文件,而文件是硬盘上的数据)
2、需要自己加锁处理
因此我们最好找寻一种解决方案能够兼顾:
1、效率高(多个进程共享一块内存的数据)
2、帮我们处理好锁问题。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列
主要参数
q.put():用以插入数据到队列中。
q.get():可以从队列读取并且删除一个元素。
q.full():判断队列是否已满,返回值:True、False。
q.empty():判断队列是否为空,返回值:True、False。
队列的介绍
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
maxsize是队列中允许最大项数,省略则无大小限制。
但需要明确:
1、队列内存放的是消息而非大数据
2、队列占用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小
参数举例
注意:当队列已满(为空)时,再向队列中放入(取出)值,系统会卡在,等待队列取出(放入)值。
from multiprocessing import Queue q = Queue(3) # 设置队列长度,非必填项 print(q.full()) # False print(q.empty()) # True q.put('hello') q.put({'name': 'ysg'}) q.put([1, 2, 3]) print(q.full()) # True print(q.empty()) # False print(q.get()) # hello print(q.get()) # {'name': 'ysg'} print(q.get()) # [1, 2, 3] print(q.full()) # False print(q.empty()) # True
生产着消费者模型
为什么要使用生产者消费者模型
生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者和消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的
from multiprocessing import Queue, Process import time def server(q, s): for i in range(10): res = '%s %s' % (s, i) time.sleep(0.5) print('生产 %s' % res) q.put(res) def client(q): while 1: time.sleep(1) res = q.get() if res == None: break print('消费 %s' % res) if __name__ == '__main__': q = Queue() ss = '包子' ss1 = '油条' ss2 = '饼' # 生产者们 s = Process(target=server, args=(q, ss)) s1 = Process(target=server, args=(q, ss1)) s2 = Process(target=server, args=(q, ss2)) # 消费者们 c = Process(target=client, args=(q,)) c1 = Process(target=client, args=(q,)) s_l = [s, s1, s2] c_l = [c, c1] for i in s_l: i.start() for i in c_l: i.start() for i in s_l: i.join() q.put(None) q.put(None) print('-----主进程-----')
生产者消费者模型总结
1、程序中有两类角色
一类负责生产数据(生产者)
一类负责处理数据(消费者)
2、引入生产者消费者模型为了解决的问题是
平衡生产者与消费者之间的速度差
程序解开耦合
3、如何实现生产者消费者模型
生产者<--->队列<--->消费者
JoinableQueue([maxsize])
这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
参数介绍
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍
JoinableQueue 的实例 p 除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示 q.get() 的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常。
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用 q.task_done()方法为止。
基于JoinableQueue实现生产者消费者模型
1、主进程等生产者p1、p2、p3结束
2、而p1、p2、p3是在消费者把所有数据都取干净之后才会结束
3、所以一旦p1、p2、p3结束了,证明消费者也没必要存在了,应该随着主进程一块死掉,因而需要将生产者们设置成守护进程
from multiprocessing import JoinableQueue, Process import time def server(q, s): for i in range(3): res = '%s %s' % (s, i) time.sleep(0.5) print('生产 %s' % res) q.put(res) q.join() # 等到消费者把自己放入队列中的所有的数据都取走之后,生产者才结束 def client(q): while 1: time.sleep(1) res = q.get() print('消费 %s' % res) q.task_done() # 发送信号给q.join(),说明已经从队列中取走一个数据并处理完毕了 if __name__ == '__main__': q = JoinableQueue() ss = '包子' ss1 = '油条' ss2 = '饼' # 生产者们 s = Process(target=server, args=(q, ss)) s1 = Process(target=server, args=(q, ss1)) s2 = Process(target=server, args=(q, ss2)) # 消费者们 c = Process(target=client, args=(q,)) c1 = Process(target=client, args=(q,)) c.daemon = True c1.daemon = True s_l = [s, s1, s2] c_l = [c, c1] for i in s_l: i.start() for i in c_l: i.start() for i in s_l: i.join() print('-----主进程-----')