管道
先画一幅图帮助大家理解下管道的基本原理
现有2个进程A和B,他们都在内存中开辟了空间,那么我们在内存中再开辟一个空间C,作用是连接这两个进程的。对于进程来说内存空间是可以共享的(任何一个进程都可以使用内存,内存当中的空间是用地址来标记的,我们通过查找某一个地址就能找到这个内存)A进程可以不断的向C空间输送东西,B进程可以不断的从C空间读取东西,这就是进程间的通信
这个通信方式的原理类似于用文件通信,一个进程往文件里写,另外一个进程从文件里读取,最大的不同在于管道里C的空间是开辟在内存当中,第一是他不容易被截获,第二是进程结束后内存空间他会自动的释放掉,文件就不能自动释放掉。第三就是内存的传输是不需要和磁盘交互,所以相比文件管道的传输效率要高很多,这就是管道的基本原理。
管道在信息传输上是以流的方式传输, 也就是你从A进程不断的写入,B进程源源不断的读出,A进程先写入的就会被B进程先读出,后写进来的就会被后读出,管道不仅仅可以让A进程写C内存空间收,也可以B进程写,C内存空间收,也就是双向通信,单项通信叫做半双工,双向叫做全双工
2.管道本质
在内存中开辟一个新的空间,对多个进程可见,在通信形式上形成一种约束
3.创建管道
multiprocessing------------->Pipe
1.首先理解Pipe是什么,有人理解为类,那到底是什么?
>>> from multiprocessing import Pipe
>>> type(Pipe)
<class 'method'>
可以看到是个方法,也就是函数,是通过函数的返回值来完成工作的,不是类
2.下下来看具体事例:
import os,time
from multiprocessing import Pipe,Process
#进程函数
def func(name):
time.sleep(1)
print('父进程的id为:',os.getppid(),"--------",'子进程的id为:',os.getpid())
#创建五个进程
if __name__ == '__main__':
job = []
for i in range(5):
p = Process(target=func,args=(i,))
#把新的进程添加到列表里
job.append(p)
p.start()
for i in job:
i.join()
打印结果为:
父进程的id为: 18416 -------- 子进程的id为: 16152
父进程的id为: 18416 -------- 子进程的id为: 17436
父进程的id为: 18416 -------- 子进程的id为: 1832
父进程的id为: 18416 -------- 子进程的id为: 10232
父进程的id为: 18416 -------- 子进程的id为: 13716
结果是过了一秒同时打印这五句话,因为5个进程各自执行,都是一秒后执行
接下来演示管道是如何操作的
Pipe(duplex)
功能:创建一个管道
参数:duplex默认值为True,表示管道为双向管道(全双工)如果设置为False则为单项管道(半双工)
返回值:返回两个管道流对象,两个管道流对象分别表示管道的两端,如果参数为True的时候,两个对象均可发送接收,如果为False时,则第一个对象只能接收,第二个就只能发送
情况一:在上面代码的基础上修改,参数为True的情况
import os,time
from multiprocessing import Pipe,Process
#进程函数()
#a为形参,代表着child_conn
def func(name,a):
time.sleep(1)
#把字符串往管道里发送
a.send('博主最帅'+str(name))
print('父进程的id为:',os.getppid(),"--------",'子进程的id为:',os.getpid())
#创建五个进程
if __name__ == '__main__':
# 创建管道对象
# 管道函数返回了两个对象
child_conn,parent_conn = Pipe()
job = []
for i in range(5):
p = Process(target=func,args=(i,child_conn))
#把新的进程添加到列表里
job.append(p)
p.start()
#从管道中接收,此时写在了join前面,意味着这个for循环时和所有子进程同步进行的
for i in range(5):
data = parent_conn.recv()
print(data)
for i in job:
i.join()
打印结果为:
博主最帅2
父进程的id为: 12048 -------- 子进程的id为: 12232
博主最帅3
博主最帅1
父进程的id为: 12048 -------- 子进程的id为: 8168
父进程的id为: 12048 -------- 子进程的id为: 9708
博主最帅4
父进程的id为: 12048 -------- 子进程的id为: 3984
博主最帅0
父进程的id为: 12048 -------- 子进程的id为: 11712
解析:一个父进程,创建了5个子进程,5个子进程都往管道里写数据,父进程这边做接收(5个子进程往进写,就要接收5次,所以for i in range(5):data=xxx),把接收写在了join前面,意味着接收这个循环时和所有子进程同步进行。那么有个问题,在睡眠期间,这个父进程有没有执行,答案时当然在执行,那为什么没有打印出来呢?不打印说明了父进程遇到了阻塞,在parent_conn.recv()这阻塞了,recv()是阻塞函数,recv()是要从管道里往出拿东西,但是呢,子进程往进放东西前一直在睡眠,那么主进程就一直从往下执行,直到data部分,管道是空的,这个时候他就阻塞了,所以recv是一个阻塞函数(当管道为空的时候就会阻塞)。当睡眠时间过去后,5个子进程同步执行,都往管道里放东西,谁先放,谁后放这个不一定,下面主进程这块也是同步执行的,随时接收到东西,随时打印出来,这就是为什么运行结果是这样的
2.把上面代码稍作修改:
import os,time
from multiprocessing import Pipe,Process
#进程函数()
#a为形参,代表着child_conn
def func(name,a):
time.sleep(1)
#把字符串往管道里发送
a.send('博主最帅'+str(name))
print('父进程的id为:',os.getppid(),"--------",'子进程的id为:',os.getpid())
#创建五个进程
if __name__ == '__main__':
# 创建管道对象
# 管道函数返回了两个对象
child_conn,parent_conn = Pipe()
job = []
for i in range(5):
p = Process(target=func,args=(i,child_conn))
#把新的进程添加到列表里
job.append(p)
p.start()
#从管道中接收,此时写在了join前面,意味着这个for循环时和所有子进程同步进行的
#for i in range(5):
time.sleep(5)
data = parent_conn.recv()
print(data)
for i in job:
i.join()
打印结果为:
父进程的id为: 11916 -------- 子进程的id为: 15404
父进程的id为: 11916 -------- 子进程的id为: 11852
父进程的id为: 11916 -------- 子进程的id为: 1648
父进程的id为: 11916 -------- 子进程的id为: 4736
父进程的id为: 11916 -------- 子进程的id为: 15588
博主最帅3
上面子进程睡眠了1秒钟,5个子进程都把东西放到了管道里,也就是5个子进程在一秒之后全部执行完毕,主进程睡眠5秒之后,只接收一次就出现了上面的打印结果,只接收到了一个,你从管道里send进去5个内容,但是我只有一个recv()函数,就只接收一次
4.总结:
1.向管道发送数据使用send函数,从管道接收数据使用recv()函数
2.recv()函数为阻塞函数,当管道中数据为空的时候会阻塞
3.一次recv()只能接收一次send()的内容
4.send可以发送的数据类型比较多样,字符串,数字,列表等等