三、进程间通信
进程间通信
进程间是相互隔离的,这体现在内存空间上的隔离
不同进程各自保存一份全局变量,不会共享全局变量
# 验证进程间是相互隔离的
from multiprocessing import Process
a = 100
def func():
global a #局部修改全局
a = 200
print('子',a) #200
#实际上 子进程 只是修改了自己全局中变量a 的值为 200,对于父进程中的变量他根本无法操作
if __name__ == '__main__':
p = Process(target = func)
p.start()
'''
如果进程间是默认是可以通信的,那么 子进程的func方法 就可以修改到主进程中的 变量a ,那么下面的print(a)应该输出200,如果不是200,则代表进程间不可通信
'''
p.join() #为子进程的操作提供时间
print(a) #100
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
1、Queue类-队列
队列 = 管道+锁
简介
'''
队列:先进先出
堆栈:先进后出
'''
Queue底层就是以管道和锁定的方式实现
'''
Queue是一个先进先出(First In First Out)队列,主进程中创建一个Queue对象,并作为参数传入子进程,两者之间通过put()放入数据,通过get()取出数据,执行了get()函数之后队列中的数据会被同时删除,可以使用multiprocessing模块的Queue实现多进程之间的数据传递,queue.Queue是实现多线程之间的数据传递
'''
- 语法:
from multiprocessing import Queue
# 创建一个队列对象
q = Queue([int])
'''
参数介绍:
int:此int数值作为设置queue队列长度,即限制在没有使用get取出put中数据时,put最多能够传输的最大数据长度,默认值为2147483647,可以看做无限制
'''
Queue 模块中的常用方法:
- Queue.qsize() 返回队列的大小
- Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False
- Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait() 相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- Queue.join() 实际上意味着等到队列为空,再执行别的操作
1.1 put()
向队列中存入数据,一个put()就代表一个队列长度,如果该put数据被get从队列中取出,那么队列中的数据会被同时删除
- 语法:
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
# 向该队列中存入数据
q.put(obj, block=True, timeout=None)
'''
参数介绍:
obj:填入需要传入给对端的数据
block:设置当队列满时,是否阻塞程序
True:阻塞
False:不阻塞,且队列满时,会立即抛出queue.Full异常。
timeout:只有当block=True时有效,设置当队列满时,阻塞的时间,如果超时该队列仍未有剩余空间,则抛出queue.Full异常
int:填入大于0的整数
注意:
put()方法是一个阻塞的方法,当队列数据放满之后,如果还有数据要放入,那么put会阻塞程序,直到队列有位置让出,不然会一直阻塞
'''
1.2 put_nowait()
作用同 put(block=False),当队列满时,立马抛出queque.Full异常
- 语法:
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
# 向该队列中存入数据
q.put_nowait(obj)
'''
参数介绍:
obj:填入需要放入队列的数据
'''
1.3 get()
从队列中取出数据,存取数据都按照FIFO(先入先出原则),如果队列中没有数据,则会阻塞,直到有数据获取
get从队列中取出数据后,那么队列中的该数据会被同时删除
- 语法:
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
# 向该队列中存入数据
q.put(obj)
# 从队列中取出数据
q.get(block=True, timeout=None)
'''
参数介绍:
block:设置当队列中没有数据能够取出时,是否会阻塞程序
True:阻塞
False:如果Queue队列还有一个值可用,则立即返回该值;否则,如果队列为空,则立即抛出Queue.Empty异常
timeout:只有当block=True时有效,当队列中没有数据能够取出时,会等待timeout设置的时间,如果等待时间内没有取到任何元素,会抛出Queue.Empty异常
int:填入大于0的整数
注意:
如果队列中已经没有数据的话,get方法会原地阻塞,直到获取到数据
'''
1.4 get_nowait()
get_nowait()作用同get(block=False),当队列中没有数据能够取出时,立即抛出Queue.Empty异常
- 语法:
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
# 从队列中取出数据
q.get_nowait()
1.5 empty()
判断当前队列是否空了,返回的是一个bool类型的值
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
q.empty()
1.6 full()
判断当前队列是否满了,返回的是一个bool类型的值
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
1.7 qsize()
返回队列的大小,返回的是一个int类型的值,值与Queue([int])中int值有关
from multiprocessing import Queue
# 创建一个队列对象
q = Queue(3)
1.8 其他方法(了解)
'''
cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
join_thread():连接队列的后台线程。此方法用于在调用
close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调cancel_join_thread方法可以禁止这种行为
2、Pipe类-管道
必须在产生Process对象之前产生管道
- 语法:
from multiprocessing import Pipe
conn1, conn2 = Pipe([duplex])
'''
作用:
在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
参数介绍:
dumplex:默认管道是全双工的,如果将duplex设置为False,conn1只能用于接收,conn2只能用于发送。
True:全双工
False:半双工
'''
2.1 recv()
接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
- 语法:
from multiprocessing import Pipe
conn1, conn2 = Pipe([duplex])
conn1.recv()
2.2 send()
通过连接发送对象。obj是与序列化兼容的任意对象
向管道的另一端发送数据
- 语法:
from multiprocessing import Pipe
conn1, conn2 = Pipe([duplex])
conn1.send(obj)
2.3 其他方法
'''
conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
conn1.fileno():返回连接使用的整数文件描述符
conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。
conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收
conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
3、案例
3.1 Queue实现进程间通信
from multiprocessing import Queue, Process
"""
研究思路
1. 主进程与子进程结束队列通信
2. 子进程与子进程借助队列通信
"""
def producer(q):
q.put('我是23号技师 很高兴为您服务')
def consumer(q):
print('consumer', q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q,))
p.start()
p1 = Process(target=consumer, args=(q,))
p1.start()
'''
输出结果:
consumer 我是23号技师 很高兴为您服务
'''
3.2 Pipe实现进程间通信
注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,程序可能再消费者中的recv*()*操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。
from multiprocessing import Process,Pipe
import time,os
def consumer(p,name):
left,right=p
left.close()
while True:
try:
baozi=right.recv()
print('%s 收到包子:%s' %(name,baozi))
except EOFError:
right.close()
break
def producer(seq,p):
left,right=p
right.close()
for i in seq:
left.send(i)
# time.sleep(1)
else:
left.close()
if __name__ == '__main__':
left,right=Pipe()
c1=Process(target=consumer,args=((left,right),'c1'))
c1.start()
seq=(i for i in range(10))
producer(seq,(left,right))
right.close()
left.close()
c1.join()
print('主进程')
4、生产者消费者模型
'''
生产者(生产商品) + 消息队列(存储商品) + 消费者(消耗商品)
'''
4.1 使用Queue实现简单消费者模型
from multiprocessing import Process, Queue
import time
import random
# 生产者
def producer(name, food, q):
for i in range(4):
data = '{}生产了{}{}'.format(name, food, i)
# 模拟延迟
time.sleep(random.randint(1, 3))
print(data)
# 将数据放入队列中
q.put(data)
# 消费者
def consumer(name, q):
while True:
try:
food = q.get(block=True, timeout=3)
time.sleep(random.randint(1, 3))
print(name, '消耗了', food, sep='')
except Exception:
break
if __name__ == '__main__':
q = Queue()
# 定义生产者
p1 = Process(target=producer, args=('大厨egon', '包子', q))
p2 = Process(target=producer, args=('马叉虫tank', '煲汤', q))
# 定义消费者
c1 = Process(target=consumer, args=('春哥', q))
c2 = Process(target=consumer, args=('沙雕', q))
# 开启进程
p1.start()
p2.start()
c1.start()
c2.start()
4.2 使用JoinableQueue实现简单消费模型
from multiprocessing import Process, JoinableQueue
import time
import random
# 生产者
def producer(name, food, q):
for i in range(4):
data = '{}生产了{}{}'.format(name, food, i)
# 模拟延迟
time.sleep(random.randint(1, 3))
print(data)
# 将数据放入队列中
q.put(data)
# 消费者
def consumer(name, q):
while True:
food = q.get(block=True, timeout=3)
time.sleep(random.randint(1, 3))
print(name, '消耗了', food, sep='')
q.task_done()
if __name__ == '__main__':
q = JoinableQueue()
# 定义生产者
p1 = Process(target=producer, args=('大厨egon', '包子', q))
p2 = Process(target=producer, args=('马叉虫tank', '煲汤', q))
# 定义消费者
c1 = Process(target=consumer, args=('春哥', q))
c2 = Process(target=consumer, args=('沙雕', q))
# 开启进程
p1.start()
p2.start()
c1.daemon = True
c1.start()
c2.daemon = True
c2.start()
p1.join()
p2.join()
q.join()
5、JoinableQueue类
该类拥有与Queue差不多的方法
- 语法:
from multiprocessing import JoinableQueue
# 创建一个进程对象,该对象可以实现Queue的大部分功能
q = JoinableQueue([maxsize])
'''
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
JoinableQueue 每当往队列中存入数据时,JoinableQueue内部会有一个计数器,并自加1,当调用task_done的时候,计数器-1
'''
5.1 join()
等待队列中所有的数据被取完才会继续向下执行代码,否则阻塞
- 语法:
from multiprocessing import JoinableQueue
# 创建一个进程对象,该对象可以实现Queue的大部分功能
q = JoinableQueue()
# 只有队列中所有的数据被取完,才会执行该代码后面的代码,否则处于阻塞状态
q.join()
'''
JoinableQueue 每当往队列中存入数据时,JoinableQueue内部会有一个计数器,并自加1,当调用task_done的时候,计数器-1
q.join() 当计数器为0的时候,才会继续执行后续代码
'''
5.2 task_done()
该进程每从队列中拿取一个数据时,都会向任务已经完成的队列发送一个信号
from multiprocessing import JoinableQueue
# 创建一个进程对象,该对象可以实现Queue的大部分功能
q = JoinableQueue()
q.task_done()
'''
JoinableQueue 每当往队列中存入数据时,JoinableQueue内部会有一个计数器,并自加1,当调用task_done的时候,计数器-1
'''