首页 > 技术文章 > 多进程

zhangmingyan 2019-09-11 03:52 原文

一 multiprocessing模块

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

  multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

一 Process类的介绍

**创建进程的类**:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
**参数介绍:**
group参数未使用,值始终为None

target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'egon',)

kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}

name为子进程的名称

  方法介绍:

p.start():启动进程,并调用该子进程中的p.run() 
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True

p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
**属性介绍:**
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 
3 p.name:进程的名称
4 
5 p.pid:进程的pid
6 
7 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
8 
9 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

注意:在windows中Process()必须放到# if name == 'main':下

Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module. 
If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources). 
This is the reason for hiding calls to Process() inside

if __name__ == "__main__"
since statements inside this if-statement will not get called upon import.
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 
这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。

二 创建子进程的两种方式

第一种

from multiprocessing import Process
import time

def task(n):
    print('子进程开始')
    time.sleep(n)
    print('子进程结束')

if __name__ == '__main__':
    p = Process(target=task,args=(1,))
    p.start()  # p.start 只是向操作系统发了一个信号,请求开启子进程,操作系统具体什么时候开,开多长时间你控制不了。
    time.sleep(5) #
    print('我是主进程')

第二种

from multiprocessing import Process
import time


class MyProcesszzz(Process):
    def __init__(self,x):# 如果不传参没必要重写init
        super().__init__()
        self.n = x

    def run(self):
        print('我是子进程 start')
        time.sleep(self.n)
        print('我是子进程 end')


if __name__ == '__main__':
    p1 = MyProcesszzz(2)
    p1.start()

    print('我是主进程')


'''
思考:主进程是什么结束的呢?
ps: 主进程会在子进程都结束的时候再结束 
why?详情见僵尸进程
'''

三 僵尸进程孤儿进程(了解)

'''引号的内容不适合初次阅读本博客思考
# 僵尸进程
1 何为没死干净的进程--》2 父亲都会有什么需求--》3 僵尸进程的概念--》4 什么时候回收pid合理--》5 之所以父等待子进程结束的原因。
# 孤儿进程
1 父进程死了谁来回收自己程? --》2 孤儿进程的概念 --》是父进程来回收好?还是init回收好?--》孤儿进程总归是无害的。
'''

#二:僵尸进程(父进程一直不死不停造子进程并且不回收僵尸进程有害)
    一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
# 二:孤儿进程(无害)
  孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

论述可能出现的情况:

情况一

1 父进程正常回收子进程 (无论父进程结束自动回收和手动回收僵尸进程均无害)

情况二

2 父进程先死了,出现了孤儿进程(可能父进程先死了 ,子进程没有死 ,子进程编程了孤儿进程由init管理回收,无害。)

情况三

3 父进程一直不死不断开启子进程,并且不手动回收。(有害)

父进程一直不死不断开启子进程,一直不发起回收僵尸进程的情况,父进程不死,init不会帮父进程回收,产生了大量的僵尸进程无人回收,占用了大量的pid,会导致其他程序无pid可用。

如何解决情况三 ?

直接杀死父进程,所有的子进程均变为子进程均变为孤儿进程,统一由init回收。

四 证明进程间内存空间隔离

# 一个进程修改代码,肯定生效
x = 0
def task():
    global x
    x = 50
    print(x)

task()
print(x)
from multiprocessing import Process
# 一个进程修改代码,肯定生效
x = 0
def task():
    global x # 子进程修改的是自己的名称空间里的x,与主进程无关。
    x = 50
    print(f'子进程的x:{x}') 
if __name__ == '__main__':    
    p = Process(target=task)
    p.start()
    print(f'主进程的x:{x}')

五 Process的join方法

引入:

from multiprocessing import Process
import time
def task():
    print('子进程 开始')
    time.sleep(2)
    print('子进程 结束')
if __name__ == '__main__':

    p = Process(target=task)
    p.start()
    time.sleep(5) # 主进程io了5s钟,子进程已经结束
    print('主线程')
    
    '''输出:
    
    子进程 开始
    子进程 结束
    主线程
    
    '''

    # 思考:有没有一种只智能的方式可以动态的等待子进程结束?

1 join具体用法

from multiprocessing import Process
import time
def task():
    print('子进程 开始')
    time.sleep(5)
    print('子进程 结束')
if __name__ == '__main__':
    start_time = time.time() # 记录开始时间
    p = Process(target=task)
    p.start()
    p.join() # 如遇join会阻塞该子进程,直到该子进程结束。并且join调用了wait方法释放僵尸进程。
    end_time = time.time() # 记录结束时间
    print(end_time-start_time) # 计算时间差
    print('主线程')
    
    '''输出:
    
    子进程 开始
    子进程 结束
    5.051951885223389
    
    主线程
    '''

2 思考:如果有多个join是否是串行了?

from multiprocessing import Process
import time
def task(n):
    print('子进程 开始')
    time.sleep(n)
    print('子进程 结束')
if __name__ == '__main__':
    start_time = time.time() # 记录开始时间
    p1 = Process(target=task,args=(2,))
    p2 = Process(target=task,args=(4,))
    p3 = Process(target=task,args=(6,))

    p1.start()
    p2.start()
    p3.start()
    # 换顺序也是一样的也是按照时长最长的那个计算。
    p1.join() # 等待2s
    p2.join() # 等待2s
    p3.join() # 等待2s
    end_time = time.time() # 记录结束时间
    print(end_time-start_time) # 计算时间差
    print('主线程')



    '''输出:
    
    子进程 开始
    子进程 开始
    子进程 开始
    子进程 结束
    子进程 结束
    子进程 结束
    7.566513776779175
    主线程
    
    '''

3 join串行的情况

from multiprocessing import Process
import time
def task(n):
    print('子进程 开始')
    time.sleep(n)
    print('子进程 结束')
if __name__ == '__main__':
    start_time = time.time() # 记录开始时间
    p1 = Process(target=task,args=(2,))
    p2 = Process(target=task,args=(4,))
    p3 = Process(target=task,args=(6,))

    p1.start()
    p1.join() 
    p2.start()
    p2.join() 
    p3.start()
    p3.join() 
    end_time = time.time() # 记录结束时间
    print(end_time-start_time) # 计算时间差
    print('主线程')

    '''输出:
    
    子进程 开始
    子进程 结束
    子进程 开始
    子进程 结束
    子进程 开始
    子进程 结束
    15.407774925231934
    主线程
    
    '''
    # ps:反而不如不开进程,正常调用三次来的快。

4 精炼代码:

from multiprocessing import Process
import time
def task(n):
    print('子进程 开始')
    time.sleep(n)
    print('子进程 结束')
if __name__ == '__main__':
    start_time = time.time() # 记录开始时间
    task_list = []
    for i in range(1,4):
        p = Process(target=task,args=(i,))
        p.start()
        task_list.append(p)
    print(task_list) # [<Process(Process-1, started)>, <Process(Process-2, started)>, <Process(Process-3, started)>]
    for i in task_list:
        i.join()
    end_time = time.time() # 记录结束时间
    print(end_time-start_time) # 计算时间差 4.764175891876221
    print('主线程')

六 Process的其他用法(了解)

pid用法:

'''
    在当前进程查看当前进程pid
        os.getpid()
        current_process().pid
    在当前进程查看子进程pid
        子进程对象.pid()
    在当前进程查看父进程pid
        os.getppid()   
'''

from multiprocessing import Process,current_process
import time,os
def task(x):
    print('进程开始')
    print('子进程对象的pid:',os.getpid()) # 在子进程中查看自己的pid
    print('子进程对象的pid:',current_process().pid) # 在子进程中查看自己的pid
    print('子进程对象的父进程对象的pid:',os.getppid()) # 查看父进程对象的pid
    time.sleep(100) # 注意要保证子进程不死,方便在cmd下测试。
    print('进程结束')

if __name__ == '__main__':
    p = Process(target=task,args=('子进程',))
    p.start()
    print('在父进程中查看子进程的pid',p.pid) # 在父进程中查看子进程的pid
    print('主进程自己查看自己的pid:',os.getpid()) # 父进程查看自己的pid
    print('主进程自己查看自己的pid:',current_process().pid) # 父进程查看自己的pid

name的用法:

from multiprocessing import Process,current_process

def task():
    print(current_process().name) # 在子进程查看自己的name

if __name__ == '__main__':
    p = Process(target=task) 
    p2 = Process(target=task)
    p3 = Process(target=task,name='rocky') # 已定义name属性为rocky
    p.start()
    p2.start()
    p3.start()
    print(p.name) #Process-1
    print(p2.name) #Process-2
    print(p3.name) #rocky

is_alive用法:

from multiprocessing import Process,current_process
import time
def task():
    print('子进程开始')
    time.sleep(1)
    print('子进程结束')

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    # p.terminate() #发送一个指令给操作系统但是不会立即结束子进程
    # time.sleep(1)
    print(p.is_alive()) # True 判断子进程代码是否结束
    time.sleep(3)
    print(p.is_alive()) # False

### 七 守护进程

主进程创建守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

例一:主进程代码运行完,守护进程立即结束

from multiprocessing import Process
import time
def foo():
    print('守护进程开始')
    time.sleep(5)
    print('守护进程开始')


if __name__ == '__main__':
    p1 = Process(target=foo)
    p1.daemon = True # 一定要凡在start之前,表示设置为一个守护进程。
    p1.start()
    print('主进程')
    
    '''输出:
    主进程
    '''
    # 守护进程一旦发现主进程代码运行完,立刻结束,并不会管自己的进程是否运行完

例二 主进程代码运行完后等在子进程运行阶段,线程不会参与守护

from multiprocessing import Process

import time
def foo():
    print('守护进程开始')
    time.sleep(5)
    print('守护进程结束')
def task():
    print('子进程开始')
    time.sleep(3)
    print('子进程开始')

if __name__ == '__main__':
    p1 = Process(target=foo)
    p2 = Process(target=task)
    p1.daemon = True # 一定要凡在start 之前
    p1.start()
    p2.start()
    # p2 = Process(target=task)
    print('主进程')

    '''输出:
    主进程
    子进程开始
    子进程开始
    '''
    #分析 守护进程在运行完主进程最后一行代码就结束,但是主进程并没有结束,主进程在等待子进程运行结束.

什么情况适合用守护进程?

当主进程代码结束,该子进程再执行无意义的情况可以用守护进程。

八 进程同步(锁)

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,

而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理

part1:多个进程共享同一打印终端

#并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process
import os,time
def work():
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())

if __name__ == '__main__':
    for i in range(3):
        p=Process(target=work)
        p.start()
#由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process,Lock
import os,time
def work(lock):
    lock.acquire()
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,))
        p.start()

part2:多个进程共享同一文件

文件当数据库,模拟抢票

from multiprocessing import Process,Lock
import os , time
import json
def search():
    with open('a.json',encoding='utf-8') as f:
        data = json.load(f)

    print(f'主页面还剩下{data["count"]}张票')

def get():
    # 来到支付页面通常会再次查看一下票数
    with open('a.json', encoding='utf-8') as f:
        data = json.load(f)
    time.sleep(0.5) # 模拟网络io
    if data['count'] > 0:
        print(f'支付页面-还剩{data["count"]}张票')
        print(f'{os.getpid()}进程, 抢到了1张票')
        time.sleep(0.5) # 模拟网络io
        with open('a.json','w',encoding='utf-8') as f:
            data['count'] -=1
            json.dump(data,f)

def task(lock):
    # search()
    lock.acquire()
    get()
    lock.release()


if __name__ == '__main__':
    lock = Lock()
    for i in range(1,20):# 模拟20个人抢票
        p = Process(target=task,args=(lock,))
        p.start()
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理



#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
1 队列和管道都是将数据存放于内存中
2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

六 队列(推荐使用)

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

 创建队列的类(底层就是以管道和锁定的方式实现)

1 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 
**参数介绍:**
1 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有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
3  
4 q.get_nowait():同q.get(False)
5 q.put_nowait():同q.put(False)
6 
7 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
8 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
9 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
其他方法(了解):
1 q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
2 q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
3 q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''

from multiprocessing import Process,Queue
import time
q=Queue(3)


#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)
q.put(3)
q.put(3)
print(q.full()) #满了

print(q.get())
print(q.get())
print(q.get())
print(q.empty()) #空了

七 生产者消费者模型

生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

基于队列实现生产者消费者模型

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()
    print('主')
#生产者消费者模型总结

    #程序中有两类角色
        一类负责生产数据(生产者)
        一类负责处理数据(消费者)
        
    #引入生产者消费者模型为了解决的问题是:
        平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
        
    #如何实现:
        生产者<-->队列<——>消费者
    #生产者消费者模型实现类程序的解耦和

此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。

解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
    q.put(None) #发送结束信号
if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()
    print('主')

注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))

def producer(q):
    for i in range(2):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()

    p1.join()
    q.put(None) #发送结束信号
    print('主')

但上述解决方式,在有多个生产者和多个消费者时,我们则需要用一个很low的方式去解决

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))

def producer(name,q):
    for i in range(2):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))



if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    p2.start()
    p3.start()
    c1.start()

    p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
    p2.join()
    p3.join()
    q.put(None) #有几个消费者就应该发送几次结束信号None
    q.put(None) #发送结束信号
    print('主')

其实我们的思路无非是发送结束信号而已,有另外一种队列提供了这种机制

   #JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

   #参数介绍:
    maxsize是队列中允许最大项数,省略则无大小限制。    
  #方法介绍:
    JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
    q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
    q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))

        q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了

def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
    


if __name__ == '__main__':
    q=JoinableQueue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))
    c1.daemon=True
    c2.daemon=True

    #开始
    p_l=[p1,p2,p3,c1,c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    p3.join()
    q.join()  # 主进程代码运行完毕--生产者运行完毕,队列也取空了--消费者没有存在的意义了。

   # 主进程代码运行完毕,某个进程应该结束掉,这种情况使用守护线程会非常边便捷。

推荐阅读