进程之间交换对象
并行:同一时刻有多条指令在多个CPU上运行.
并行时常常需要进程之间交换数据,
multiprocessing
模块提供了2个communication channels
来交换数据:队列queue
和管道pipe
除了上面两种交换数据的方式之外,进程之间也可以使用同步原语进行数据交换,但是要尽可能的避免使用同步原语,例如锁.因为
queue
和pipe
效率上更高,更加安全.
1.使用队列queue
来交换数据
queue
是多进程安全的队列,可以使用queue
来实现多进程之间的数据传递.
put
:此方法用来插入数据队列中,可选参数有两个:blocked\timeout
- 如果
blocked=True
(默认值),并且timeout
为正数,该方法会阻塞timeout
指定时间.直到该队列有剩余的空间.如果超时,会抛出Queue.Full
异常- 如果
blocked=False
,但是该队列已满,会抛出Queue.Full
异常get
:此方法可以从队列读取并删除一个元素.可选参数有2个:blocked\timeout
- 如果
blocked=True
(默认值),并且timeout
为正数,那么在等待时间内没有取到任何元素,会抛出Queue.Empty
异常- 如果
blocked=False
,如果queue
有一个值可用,立即返回该值,否则,队列为空,会立即抛出异常Queue.Empty
常用属性和方法:
qsize()
返回队列的大小 empty()
返回布尔值,队列是否为空 full()
返回布尔值,队列是否满了 put(item[,block[,timeout]])
在队列中添加元素 item
put_nowait(item)
等价与 put(item, False)
get(item[,block[,timeout]])
在队列中删除元素并返回该元素的值 get_nowait()
等价与 get(Flase)
close() 表示该 queue
不加入新元素join_theread()
加入后台线程,只能在调用 close()
后使用.它阻塞直到后台线程退出,确保缓冲区所有数据已经刷新到管道.默认情况下,如果进程不是队列的创建者,则退出.它将尝试加入队列的后台线程.cancle_join_thread()
终止 join_thread()
防止后台线程在进程退出时被自动连接,可能会导致数据丢失
队列实例
#!/usr/bin/env python
# encoding: utf-8
import multiprocessing,time,os,random
"""
queue队列
在队列中,进程是相互独立的.数据的交互,可以使用队列一个写入,一个读取
"""
def write_process(q):
for i in ['A', 'B', 'C', 'D']:
print('put {} to queue ...'.format(i))
q.put(i)
time.sleep(random.random())
def read_process(q):
while True:
if not q.empty():
value = q.get()
print('get {} from queue...'.format(value))
time.sleep(random.random())
else:
break
if __name__ == '__main__':
"""主进程"""
# 使用主进程创建队列
main_process_queue = multiprocessing.Queue()
# 创建子进程
write_process_child = multiprocessing.Process(target=write_process, args=(main_process_queue, ))
read_process_child = multiprocessing.Process(target=read_process, args=(main_process_queue, ))
# 启动并阻塞
write_process_child.start()
write_process_child.join()
read_process_child.start()
read_process_child.join()执行结果:
put A to queue....
put B to queue....
put C to queue....
put D to queue....
Get A from queue
Get B from queue
Get C from queue
Get D from queue
生产者/消费者模式
以上就类似于生产者/消费者模式
它包含两类进程:一种只是用来生产数据,例外一种只是用来消费数据.为了串联他们,通常会采用共享的数据区域,就像一个仓库.生产者产生的数据都放入仓库中并不需要关注消费者的行为,消费者只需要从共享仓库中获取数据,并不需要关心生产者的行为.
而作为仓库关注的行为是:
- 如果仓库共享数据区域已满的状态
- 如果共享数据区域已空的状态
#!/usr/bin/env python
# encoding: utf-8
import multiprocessing,os,time,random
# 定义生产者
class Producer(multiprocessing.Process):
def __init__(self, queue):
self.queue = queue
super(Producer, self).__init__()
def run(self):
for i in range(3):
item = random.randint(0, 255)
# 添加到队列
self.queue.put(item)
print('生产者进程:{},添加{}到Queue队列中...'.format(self.name, item))
time.sleep(1)
print('队列大小:{}'.format(self.queue.qsize()))
# 定义消费者
class Cunsumer(multiprocessing.Process):
def __init__(self, queue):
self.queue = queue
super(Cunsumer, self).__init__()
def run(self):
while True:
if not self.queue.empty():
time.sleep(2)
print('队列非空')
item = self.queue.get()
print('消费者进程:{},从队列中取出:{}'.format(self.name, item))
else:
print('队列为空,退出')
break
if __name__ == '__main__':
"""主进程"""
queue = multiprocessing.Queue()
producer = Producer(queue)
cunsumer = Cunsumer(queue)
producer.start()
producer.join()
cunsumer.start()
cunsumer.join()运行结果
生产者进程:Producer-1,添加227到Queue队列中...
队列大小:1
生产者进程:Producer-1,添加114到Queue队列中...
队列大小:2
生产者进程:Producer-1,添加252到Queue队列中...
队列大小:3
队列非空
消费者进程:Cunsumer-2,从队列中取出:227
队列非空
消费者进程:Cunsumer-2,从队列中取出:114
队列非空
消费者进程:Cunsumer-2,从队列中取出:252
队列为空,退出
Queue
在其中扮演了一个仓库的角色,就是用来处理共享数据的
2.使用pipe
管道
Pipe
不是类,是函数,该函数定义在multiprocessing\connection.py
中,返回一对通过管道连接的对象con1
和con2
,函数的原型是Pipe()duplex=True
dublex=True
(默认值):管道是双向的,处于全双工模式,con1\con2
都可以收发数据dublex=False
,管道是单向的,con1
只能用于发送,con2
只能用于接受
con1, con2 = multiprocessing.Pipe([duplex])
Pipe()
返回的是管道的两端,两端每个对象都有send()
和recv()
方法(还有其他方法),例如在全双工模式下,可以用con1.send()
发送消息,con2.recv()
接受消息,如果没有消息可以接受,recv()
方法会一直阻塞,如果管道已经关闭,recv()
会抛出EOFError
异常常用方法:
send(obj)
将一个对象发送到连接的另外一端 recv()
返回一个由另一端 send()
的对象,该方法会一直阻塞直到接收到对象,如果对端关闭了连接,或者没有东西可以接受,将抛出EOFError
fileno
返回由连接对象使用的描述符 close()
关闭连接对象 poll()
返回连接对象是否有可以读取的数据.
1.主进程和子进程管道通信
需要注意的是,管道在建立的时候自动连接了主进程,不管是使用全双工,还是半双工,都需要对主进程的管道进行处理.
在全双工模式中,假设主进程是发送端,子进程是接收端
"""全双工管道"""
import multiprocessing,time
def child_process(pipe):
while True:
try:
time.sleep(1)
num = pipe.recv()
print('子进程:{},接收:{}'.format(multiprocessing.current_process().name, num))
print('pipe管道状态', pipe.poll())
except Exception:
print('如果一端被关闭,另外一端接收完毕后,会报错')
break
if __name__ == '__main__':
"""主进程"""
recv_pipe, send_pipe = multiprocessing.Pipe(duplex=True)
# 主进程即是发送端,也是接收端
for i in range(5):
send_pipe.send(i)
print("主进程Pipe发送:{}".format(i))
# 发送完毕后,关闭通道
send_pipe.close()
child_process_pipe = multiprocessing.Process(target=child_process, args=(recv_pipe, ))
child_process_pipe.start()
child_process_pipe.join()执行结果:
主进程Pipe发送:0
主进程Pipe发送:1
主进程Pipe发送:2
主进程Pipe发送:3
主进程Pipe发送:4
子进程:Process-1,接收:0
pipe管道状态 True
子进程:Process-1,接收:1
pipe管道状态 True
子进程:Process-1,接收:2
pipe管道状态 True
子进程:Process-1,接收:3
pipe管道状态 True
子进程:Process-1,接收:4
pipe管道状态 True
如果一端被关闭,另外一端接收完毕后,会报错反之,主进程也能当接收端,子进程当发送端.
关闭管道后,
try
语句用于处理产生的EOFError
异常
2.主进程闲置,2个子进程一发一收
- END -在全双工模式下,就算主进程闲置,主进程也连接在管道的一端,既可以在接收端,也可以在发送端.需要自己去选择把握.
import multiprocessing
import time
def proc1(pipe):
for i in range(10):
print('send: %s' % i)
pipe.send(i) # pipe.send 发送数据
time.sleep(1)
pipe.close()
def proc2(pipe):
while True:
try:
print('proc2 recv:', pipe.recv()) # pipe.recv 用与接收数据
time.sleep(1)
except Exception:
print('exit')
break
if __name__ == '__main__':
pipe1, pipe2 = multiprocessing.Pipe()
p1 = multiprocessing.Process(target=proc1, args=(pipe1, ))
p2 = multiprocessing.Process(target=proc2, args=(pipe2, ))
p1.start()
p1.join()
# 在主进程端把发送pipe关闭
pipe1.close()
p2.start()
p2.join()关闭管道后,
try
语句用于处理产生的EOFError
异常