任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。 如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态, 但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候, 内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面 处理它的一切善后工作。因此孤儿进程并不会有什么危害。
严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
子进程是父进程的复制品,在内存中会把父进程的代码及内存分配情况拷贝一份生成子进程的运行空间,这样子进程与父进程的所有代码都一样,两个进程之间的运行时独立的,互不影响
通过如上的结果发现:
主进程都结束了,但是子进程还没有结束,直到子进程运行完成。没有join,主进程不会等待任何子进程,独立运行,不受任何其他子进程影响。通过以上发现,因为p2进程使用了join函数,所以主进程等待这个子程序运行结束之后才去执行主程序的最后一个打印工作。
以上是结果可以看到:
在主进程中首先进入for循环启动第一个子进程然后由于调用join函数会阻塞主进程,直到第一个子进程执行完成后开始取消阻塞。继续执行主进程,开始开启第二个子进程,然后由于第二个子进程也调用了join,再阻塞主进程一直到循环结束。最终循环下来,直到所有子进程,都一个一个执行完之后,才开始执行主进程。这种方式可以实现效果,但是效率太低,既然是一个一个进程执行的,和我们使用一个进程for循环是一样的效果,没有体现多核CPU并发异步执行任务的效果。通过如上的改进,我们先启动所有子进程,所有任务都启动完成之后,再使用join()这样可以实现多线程的异步执行(并发的概念)。效率也提高很多
不设置守护进程的时候,虽然主进程没有等待子进程,直接先结束了。但是,前面我们提到过init进程的作用,它会接收孤儿进程,让孤儿进程继续执行下去。
通过如上的结果:
设置了守护进程,主进程还是没有等待子进程,直接结束了。但是,主进程结束了,子进程也跟着结束了。也就说明,设置守护进程之后,init进程,就不会去接收这个孤儿进程了。这个是进程是否被设置为守护进程的最大区别。daemon一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
多进程里面使用的队列(Queue)是multiprocessing模块里面的Queue,和queue.Queue的队列模块不一样
import time from multiprocessing import Process from multiprocessing import Queue def producer(food, q, name): for i in range(3): res = '%s%s' % (food, i) time.sleep(2) q.put(res) print('%s 生产了%s' % (name, res)) q.put(name) def consumer(q, name): while True: res = q.get() if res in ("worker_01", "worker_02", "worker_03"): print(res, "-------???-------") break time.sleep(3) print('----------%s消费了%s' % (name, res)) if __name__ == '__main__': q = Queue() p1 = Process(target=producer, args=('包子', q, 'worker_01')) p2 = Process(target=producer, args=('水饺', q, 'worker_02')) p3 = Process(target=producer, args=('馒头', q, 'worker_03')) c1 = Process(target=consumer, args=(q, 'Consumer_01')) c2 = Process(target=consumer, args=(q, 'Consumer_02')) p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() p2.join() p3.join() print('主...') worker_01 生产了包子0 worker_02 生产了水饺0 worker_03 生产了馒头0 worker_03 生产了馒头1 worker_02 生产了水饺1 worker_01 生产了包子1 ----------Consumer_01消费了包子0 ----------Consumer_02消费了水饺0 worker_03 生产了馒头2 worker_01 生产了包子2 worker_02 生产了水饺2 主... ----------Consumer_01消费了馒头0 ----------Consumer_02消费了包子1 ----------Consumer_01消费了馒头1 ----------Consumer_02消费了水饺1 ----------Consumer_01消费了水饺2 ----------Consumer_02消费了包子2 worker_03 -------???------- ----------Consumer_01消费了馒头2 worker_02 -------???-------通过如上的结果:
我们通过一种将队列里面input()固定的参数的形式,如果遇到这个参数,就break就可以停止从队列里面get()信息,也避免因队列为空的时候,再获取信息的报错。但这种方式容易有个弊端,通过如上的结果是看不出的(生产9个产品,消费了9个)。但如果如上的代码只有一个消费者,得到的结果如下: worker_01 生产了包子0 worker_03 生产了馒头0 worker_02 生产了水饺0 worker_01 生产了包子1 worker_03 生产了馒头1 worker_02 生产了水饺1 ----------Consumer_01消费了包子0 worker_01 生产了包子2 worker_03 生产了馒头2 worker_02 生产了水饺2 主... ----------Consumer_01消费了水饺0 ----------Consumer_01消费了馒头0 ----------Consumer_01消费了包子1 ----------Consumer_01消费了馒头1 ----------Consumer_01消费了水饺1 ----------Consumer_01消费了水饺2 ----------Consumer_01消费了包子2 worker_02 -------???-------如上结果,生产了9个,但是只消费了8个就结束了,这个不是我们想要的结果, 原因是我们将产品input到队列的时候,产品和特殊字符的顺序可能不一致,可能有多种多样的结果,例如如下几种情况:
order_product = ["包子0", "水饺0", '馒头0', '包子1', '水饺1', '馒头1', '包子2', 'worker_01', '水饺2', 'worker_02', '馒头2', 'worker_03']如上这种情况,如果只有2个消费者,那么只能消费8个产品,如果一个消费者可以消费7个产品,因为遇到特殊字符(worker_01或worker_02等就直接结束消费了),所以使用Queue来进程间的通讯,只适合非常简答的场景。
(1)消费者不需要判断从队列里拿到某个特定字符(None或其他),再退出执行消费者函数了。 (2)消费者每次从队列里面q.get()一个数据,处理过后就使用队列.task_done() (3)生产者for循环生产完所有产品,需要q.join()阻塞一下,对这个队列进行阻塞。意思是只有当队列里面的所有内容都取完了,才会调用主进程,继续执行下去 (4)启动一个生产者,启动一个消费者,并且这个消费者做成守护进程,然后生产者需要p.join()阻塞一下。
import time import random from multiprocessing import JoinableQueue from multiprocessing import Process def producer(food, q, name): for i in range(3): res = '%s%s' % (food, i) time.sleep(random.randint(1, 3)) q.put(res) print('%s 生产了%s' % (name, res)) def consumer(q, name): while True: res = q.get() time.sleep(random.randint(2, 4)) print('----------%s消费了%s' % (name, res)) q.task_done() if __name__ == '__main__': q = JoinableQueue() p1 = Process(target=producer, args=('包子', q, 'worker_01')) p2 = Process(target=producer, args=('水饺', q, 'worker_02')) p3 = Process(target=producer, args=('馒头', q, 'worker_03')) c1 = Process(target=consumer, args=(q, 'Consumer_01')) c2 = Process(target=consumer, args=(q, 'Consumer_02')) p1.start() p2.start() p3.start() c1.daemon = True c2.daemon = True c1.start() c2.start() p1.join() p2.join() p3.join() q.join() print('主...') worker_02 生产了水饺0 worker_01 生产了包子0 worker_03 生产了馒头0 ----------Consumer_01消费了水饺0 worker_01 生产了包子1 worker_02 生产了水饺1 worker_03 生产了馒头1 ----------Consumer_02消费了包子0 worker_02 生产了水饺2 worker_01 生产了包子2 ----------Consumer_02消费了包子1 ----------Consumer_01消费了馒头0 worker_03 生产了馒头2 ----------Consumer_02消费了水饺1 ----------Consumer_01消费了馒头1 ----------Consumer_02消费了水饺2 ----------Consumer_02消费了馒头2 ----------Consumer_01消费了包子2 主...通过如上发现: 1.q.join()阻塞了队列,只有队列的信息都取完之后,才能调用主进程结束 2.q.task_done()的作用是,每次从队列里面取出内容之后,使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常。(个人理解,这个函数的作用就是告诉主进程,我又取出来一个函数,直到主进程发现队列里面没有了任何信息,此时主进程才会执行,否者主进程就一直等待子进程的执行) 3.如果如上代码不使用q.task_done(),那最终生产和消费都正常,但是主进程一直无法结束,一直在子进程的死循环中运行。
最终发现,使用第二种方式,直接创建进程池,然后把任务需要执行的数据,放在一个列表里面传递给函数即可。
虽然启动的进程池,但是所有进程还是一个一个执行的,和单进程没啥区别,不推荐使用。
每次启动5个进程,去执行task任务,分4个批次执行完成。基本实现了进程的并发
文章来源: https://www.cnblogs.com/lich1x/p/10235610.html https://blog.csdn.net/weixin_43751285/article/details/92837030 https://www.cnblogs.com/gengyi/p/8564052.html