进程介绍:对于操作系统来说,一个任务就是一个进程,比如打开浏览器,打开记事本
有些进程还不止同时干一件事,比如Word,它可以同时进行打字,拼写检查,打印等事情。在一个进程内部,要同时干多件事,就需要同时运行
多个“子任务”,我们把进程内的这些“子任务”称为线程。
线程是最小的执行单元,而进程由至少一个线程组成,如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
参考链接:https://www.cnblogs.com/linhaifeng/articles/7428874.html
(上面这个参考链接写的非常全面)
进程之间不共享数据,当前进程修改的数据,改动仅限于该进程内。
Python中的多线程无法利用多核优势,如果想充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。
注意:在windows中Process()必须放到if __name__=='__main__':下
Process类的介绍:
Process([group [,target[,name[,args[,kwargs]]]]])
参数介绍:
traget表示调用对象,即子进程要执行的任务
args表示调用对象的位置元素元组
name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义的类中一定要实现该方法
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态),timeout是可选的超时时间,需要强调的是
p.join只能join住start开启的进程,而不能join住run开启的进程。
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己
的新进程,p.daemon必须在p.start()之前设置。
进程之间的内存空间是隔离的:
打印结果:
Process对象的join方法:
join是等待进程结束,那么到底是让谁等呢?p1.join()表示让让主线程等待p1的结束,卡住的是主线程而非进程p1,
下面的p2.join()同样的表示让主线程等待p2进程结束,卡住的依旧是主线程。
join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其余p2,p3,p4仍然在运行,等#p1.join结束,可能p2,p3,p4
早已经结束了,所以4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间。
上述启动进程与join进程可以简写为;
注意为进程重命名和调用父类初始函数的顺序,看下面代码:
进程同步(锁)
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,
竞争带来的结果就是错乱,如何控制,就是加锁处理。
运行结果:
采用加锁的方式,并发变成了串行,牺牲了运行效率,但避免了竞争
运行结果:
总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了
速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1,效率低(共享数据基于文件,而文件是硬盘上的数据) 2,需要自己加锁处理
因此我们最好找到一种解决方案能够兼顾:1,效率高(多个进程共享一块内存的数据)2,帮我们处理好锁问题。这就是mutiprocessing模块
为我们提供的基于消息的IPC通信机制:队列和管道。
1,队列和管道都是将数据存放于内存中。
2,队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列
进程彼此之间互相隔离,要实现进程间通信,multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。
创建队列的类(底层就是以管道和锁定的方式实现):
1,Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
1,q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞
timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,若该Queue已满,会立即抛出Queue.Full异常。
2,q.get方法可以从队列读取并且删除一个元素,同样,get方法有两个可选参数,blocked和timeout。如果blocked为True(默认值),并且timeout为正值,
那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值。否则,
如果队列为空,则立即抛出异常。
生产者消费者模式:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产
完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和
消费者的处理能力。
但是此时有一个问题:get()不知到什么时候结束,如果当下没有东西,但是有可能put()还没结束,所以即便没有任务了,也要一直在取,这个时候就需要
put函数当生产完之后做一个通知,只有这样才知道什么时候该结束。
看下面的例子:
结束信号,也可以在主进程中,等待生产者结束,由主进程发结束信号。
看下面的例子:
那么如果说有多个生产者,有多个消费者,那是不是就要等待所有的生产者结束之后(多次join),发送结束信号,有几个消费者就发送几次
有一种队列提供了发送结束信号的机制。
JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号
和条件变量来实现的。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中
删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止。
等待p1,p2,p3结束主进程才执行,等待c1,c2结束p1,p2,p3才算结束
p1,p2,p3结束了,证明c1,c2肯定全部收完了p1,p2,p3发到队列的数据。
因而c1,c2也没有存在的价值,应该随着主进程的结束而结束,所以设置成守护进程
进程池:
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现
并发的手段之一,需要注意的问题是:
1,很明显需要并行执行的任务通常要远大于合核数
2,一个操作系统不可能无限开启进程,通常有几个核就开几个进程
3,进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)
例如当被操作对象数目不大时,可以直接利用mutiprocessing中的Process动态生成多个进程,十几个还好,但如果是上百个,上千个。。。
手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程
Pool([numprocess[,initializer[,initargs]]]):创建进程池
参数介绍:
numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值。
主要方法:
p.apply(func[,args[,kwargs]]):在一个池中执行func,但是此操作并不是在池中的所有进程都执行(也就是并不是并发执行)
p.apply_async(func[,args[,kwargs]]):在一个池中执行func。此方法会调用池中的所有进程(也就是并发执行的)
p.close():关闭进程池,防止进一步操作,如果所有操作持续挂起,它们将在工作进程终止前完成。
p.join():等待所有工作进程退出,此方法只能在close()或teminate()之后调用。