multiprocessing, 不是一个模块, 而是python中一个操作, 管理进程的包, 在这个包中几乎包含了和进程有关的所有子模块.
大致可以分为四个部分: 创建进程, 进程同步, 进程池, 进程之间数据共享.
强调: 进程没有任何共享状态, 进程修改的数据, 仅限于进程内部, 但是通过一些特殊的方法可以实现进程之间数据的共享.
1. Process模块介绍:
Process模块是一个创建进程的模块, 借助这个模块可以完成进程的创建.
在Windows下执行程序的时候, 要加上 if __name__ == "__main__": 否则会出现子进程中运次的时候还开启子进程, 出现无限循环的创建进程, 会报错.
Process中的参数介绍:
target 表示调用对象, 即子进程要执行的任务;
args 表示调用对象的位置参数元组, args = (1, 2, )
kwargs 表示调用对象的字典, kwargs = {"name": alex}
name为子进程的名称
Process中各方法的介绍:
p.start() 启动程序, 并调用该子进程的p.run()
p.run() 进程启动时运行的方法, 它去调用target指定的函数, 我们自定义的类中一定要实现这个方法
p.terminate() 强制终止进程p, 不会进行任何清理操作, 如果p创建了子进程, 该子进程就成了僵尸进程, 使用该方法时需要特别小心, 如果p保存了一个锁那么也将不会被释放, 进而导致死锁.
p.is_alive() 若p还在运行, 则返回True
p.join() 主进程等待p终止, 主进程处于等的状态, 子进程p处于运行的状态, p.join只能join住start开启的进程, 而不能join住run开启的进程
join 例子: 让主进程加上join的地方等待, 也就是阻塞住, 等待子进程执行完之后, 再继续往下执行主进程, 当主进程需要子进程的执行结果是, 就要等待. join感觉就是将子进程和主进程拼接起来, 将异步改为同步执行. 当需要所有的子进程异步执行, 然后所有的子进程全部执行完后再执行主进程时, 可以join和for循环一起使用.
Process中自带封装的属性介绍:
p.daemon: 默认值为False, 如果设为True, 代表p为后台运行的守护进程, 当p的父进程终止时, p也随之终止, 而且设置为True后, p不能创建自己的新进程, 必须在p.start()之前设置.
p.name: 进程的名称
p.pid: 进程的id
2. Process的使用:
注意: 在Windows中, Process()必须放到 if __name__ == "__main__": 下
进程的创建第二种方法(继承): 自定义一个类, 继承Process, 必须要有run方法, 通过__init__可以传参数, 但是需要super.__init__().
进程之间的内存空间是隔离的, 即数据不共享.
Process的其他方法或属性:
p.terminate() 关闭进程, 不会立即关闭, 有个等待操作系统关闭这个进程的时间, 所有is_alive立刻查看的结果可能还是存活的
3. 守护进程:
守护进程会在主进程代码执行结束后就终止
守护进程内无法再开启子进程, 否则会抛出异常,
进程之间是互相独立的, 主进程代码运行结束, 守护进程随即终止.
p.daemon = True 一定要在p.start()之前设置
4. 进程同步(锁):
虽然并发编程能更加充分的利用IO资源, 但是也带来了新的问题: 进程之间的数据不共享, 但是共享同一个文件系统, 所以访问同一个文件, 或同一个打印终端时是没有问题的, 但是共享带来了竞争, 而竞争带来的结果就是错乱, 为了加以控制, 进行加锁处理.
加锁, 保证每次只有一个进程在执行锁里面的程序, 由并发变成了串行, 牺牲了运行效率, 但是避免了竞争. 解锁之后, 其他进程才能去执行自己的程序.
5. 队列:
进程彼此之间互相隔离, 要实现进程之间的通信, 可以使用队列和管道, 这两个方式都是使用消息传递的, 队列就像一个特殊的列表, 可以设置固定长度, 先进先出.
方法介绍:
q = Queue(maxsize) 创建共享的进程队列, maxsize是长度
q.get() 返回队列中的一个数据, 如果队列为空, 则会阻塞, 直到队列中有数据为止.
q.get(False) 同get 但是如果队列为空, 不会阻塞
q.get_nowait() 同get(False)
q.put() 向队列里放数据, 如果队里已满, 会阻塞, 直到有空间可用
q.qsize() 返回队列中当前数据的数量
q.empty() 判断队列是否为空, 结果不可靠
q.full 判断队列是否已满, 结果也是不可靠的
队列里的数据是安全的, 同一时间只能一个进程拿到队列中的一个数据.
6. 生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题. 生产者和消费者之间不直接通讯, 而是通过阻塞队列来进行通讯, 所以生产者生产完数据之后不用等待消费者处理, 而是直接扔给阻塞队列, 消费者不找生产者要数据, 而是直接从阻塞队列里取, 阻塞队列就相当于一个缓冲区, 平衡生产者和消费者的处理能力, 并且可以根据生产速度和消费速度来均衡一下多少生产者可以为多少个消费者提供足够的服务, 可以开多进程, 而这些进程都是到阻塞队列中去获取或者添加数据.
程序中有两类角色: 负责生产数据的生产者, 负责处理数据的消费者
引入生产者消费者模型的目的: 平衡生产者与消费者之间的工作能力, 从而提高程序整体处理数据的速度
具体实现: 生产者 <--->队列<--->消费者
发送结束信号None的两种方式: 子进程生产者在生产完毕后发送结束信号None
主进程在生产者生产完毕后发送结束信号None
7. JoinableQueue([maxsize])
类似一个Queue对象, 但队列允许项目的使用者通知生产者项目已经被成功处理, 通知进程是使用共享的信号和条件变量来实现的.
maxsize参数: 是队列中允许的最大项数, 省略则无大小限制
方法介绍:
q.task_done(): 使用者使用此方法发出信号, 表示q.get()的返回项目已经被处理, 如果调用此方法的次数大于从队列中删除项目的数量, 将引发异常
q.join(): 生产者调用此方法进行阻塞, 直到队列中所有的项目都被处理, 阻塞将持续到队列中的每个项目均调用q.task_done()方法.