进程间通信(IPC)
1.必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
- 常用进程间通信方法
- 无名管道
特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
问题:多进程用同一管道通信容易造成交叉读写问题- 有名管道
FIFO,方向为单向(双向需要两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞- 消息队列
可用于同一主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递- 共享内存
可实现通一主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争- 信号
- 信号量
- 套接字
- 文件
文件是进程安全的
同一主机进程间的同步机制:信号量
同一主机进程间的异步机制:信号
多进程共享内存:
最简单的就是文件,文件时进程安全的
若数据量不时很大,可以用python自带的Queue,Pipe, Manager等
若数尽量很大的话,使用中间媒介,如:数据库,redis
网络主机间数据交互:
Socket套接字
管道通信(Pipe)
- 通信原理
在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信
- 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
如果是双向管道均可读写
如果是单向管道fd1只读 fd2只写
fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据
fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据
示例
from multiprocessing import Pipe, Process
import os
fd1, fd2 = Pipe()
jobs = []
def send_msg(name):
fd1.send({name: os.getpid()})
for i in range(5):
p = Process(target=send_msg, args=(i,))
jobs.append(p)
p.start()
for i in range(5):
data = fd2.recv()
print(data)
for i in jobs:
i.join()
消息队列
- 通信原理
在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
- 实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象
q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 False为非阻塞
timeout 超时检测
q.get([block,timeout])
功能:从队列取出消息
参数:block 设置是否阻塞 False为非阻塞
timeout 超时检测
返回值: 返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
q.close() 关闭队列
示例:
from multiprocessing import Queue
from time import sleep
from random import randint
p = Queue()
def request():
for i in range(20):
x = randint(0, 100)
y = randint(0, 100)
q.put((x, y))
def handle():
while True:
sleep(0.5)
try:
x, y = q.get(timeout=3)
except:
break
else:
print("%d +%d = %d" % (x, y, x + y))
p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
--------------------------------------------
共享内存
-
通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。
-
实现方法
- Value:将一个值存放在内存中,
- Array:将多个数据存放在内存中,但要求数据类型一致
from multiprocessing import Value,Array
obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype 表示共享内存空间类型 'i'表示singed int类型, 'f'表示float类型, 'c'“表示char类型
data 共享内存空间初始数据
返回值:共享内存对象
obj.value 对该属性的修改查看即对共享内存读写
obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype 表示共享内存数据类型
data 整数则表示开辟空间的大小,其他数据类型 表示开辟空间存放的初始化数据
返回值:共享内存对象
Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。
* 可以使用obj.value直接打印共享内存中的字节串
'''
Value实例
'''
from multiprocessing import Value,Process
import time
import random
# 创建共享内存
money = Value('i',5000)
#操作共享内存
def man():
for i in range(30):
time.sleep(0.2)
money.value += random.randint(1,1000)
def girl():
for i in range(30):
time.sleep(0.15)
money.value -= random.randint(100,800)
m = Process(target=man)
g = Process(target=girl)
g.start()
m.start()
m.join()
g.join()
print("一月余额:",money.value)
'''
Array实例
'''
from multiprocessing import Process,Array
shm = Array('c',['dd','kk'])
def fun():
for i in shm:
print(i)
p = Process(target=fun)
p.start()
p.join()
- Manager
Manager是通过共享进程的方式共享数据,它支持的数据类型比Value和Array更丰富。
#数据类型
value= Manager().Value() # 值对象
list_ = Manager().list() # 列表对象
dict_ = Manager().dict() # 字典对象
array = Manager().Array () # 数组对象
queue = Manager().Queue() # 队列
lock = Manager().Lock() # 普通锁
rlock = Manager().RLock() # 可冲入锁
cond = Manager().Condition() # 条件锁
semaphore = Manager().Semaphore() # 信号锁
event = Manager().Event() # 事件锁
namespace = Manager().Namespace() # 命名空间
boundedSemaphore = Manager().BoundedSemaphore() # 有限制的信号锁
示例:
from multiprocessing import Process, Manager
from time import sleep
class Test:
def __init__(self):
self.id = 12
def read(d):
sleep(5)
d['1'] = 123
d['obj'] = Test()
def calculation(manager_dict):
while 1:
if manager_dict.get('obj'):
print('res',manager_dict.get('obj').id)
break
else:
print('...')
sleep(1)
if __name__ == '__main__':
p_list = []
manager_dict = Manager().dict()
p1 = Process(target=read,args=(manager_dict,))
p1.start()
p_list.append(p1)
p2 = Process(target=calculation, args=(manager_dict,))
p2.start()
p_list.append(p2)
for res in p_list:
res.join()
这里重点介绍的是Manager().Namespace()。它会开辟一个空间,在这个命名空间中,可以更“随性”使用Python中的数据类型,访问这个空间只需要对象名.xxx即可。像下面这样:
from multiprocessing import Process, Manager
def producer(namespace): # 生产者生产数据
namespace.name = "zty"
namespace.info = {"Id": 12345, "Addr": "chengdu"}
namespace.age = 19
def consumer(namespace):
import time
time.sleep(1)
print(namespace.name)
print(namespace.info)
print(namespace.age)
if __name__ == "__main__":
namespace = Manager().Namespace()
proProcess = Process(target=producer, args=(namespace,))
conProcess = Process(target=consumer, args=(namespace,))
...
# 输出:
zty
{'Id': 12345, 'Addr': 'chengdu'}
19
不过它有一个缺点:无法直接修改可变类型的数据。拿list举例,即便是在一个子进程中修改了命名空间中列表的值,然而在另一个子进程中获取这个列表,得到的依然是未修改之前的数据。
def producer(namespace):
namespace.nums[2] = 3 # nums = [5, 1, 3]
def consumer(namespace):
time.sleep(1)
print(namespace.nums) # 输出:[5, 1, 2]
if __name__ == "__main__":
namespace = Manager().Namespace()
namespace.nums = [5, 1, 2]
namespace.alphas = ["z", "t", "y"]
proProcess = Process(target=producer, args=(namespace,))
conProcess = Process(target=consumer, args=(namespace,))
...
解决方法,更新列表引用(重新赋值):
def producer(namespace): # 生产者生产数据
nums = namespace.nums
nums[2] = 3
namespace.nums = nums
def consumer(namespace):
time.sleep(1)
print(namespace.nums) # 输出:51
本地套接字
-
功能 : 用于本地两个程序之间进行数据的收发
-
套接字文件 :用于本地套接字之间通信时,进行数据传输的介质。
-
创建本地套接字流程
【1】 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
【2】 绑定本地套接字文件
sockfd.bind(file)
【3】 监听,接收客户端连接,消息收发
listen()–>accept()–>recv(),send()
'''
发送端
'''
from socket import *
# 确保两边使用相同的套接字文件
sock_file = "./sock"
sockfd = socket(AF_UNIX,SOCK_STREAM)
sockfd.connect(sock_file)
while True:
msg = input(">>")
if not msg:
break
sockfd.send(msg.encode())
sockfd.close()
'''
接收端
'''
from socket import *
import os
# 确定本地套接字文件
sock_file = "./sock"
# 判断文件是否存储,存在就删除
if os.path.exists(sock_file):
os.remove(sock_file)
# 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
# 绑定本地套接字
sockfd.bind(sock_file)
# 监听,链接
sockfd.listen(3)
while True:
c,addr = sockfd.accept()
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.close()
sockfd.close()
信号量(信号灯集)
-
通信原理
给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。 -
实现方法
from multiprocessing import Semaphore
sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象
sem.acquire() 将信号量减1 当信号量为0时阻塞
sem.release() 将信号量加1
sem.get_value() 获取信号量数量
'''
信号量
'''
from multiprocessing import Semaphore,Process
from time import sleep
import os
#创建信号量
#服务程序最多允许三个进程同时执行事件
sem = Semaphore(3)
def handle():
print("%d 想执行时间"%os.getpid())
# 想执行事件,必须获取信号量
sem.acquire() #减少信号量
print("%d 开始执行操作"%os.getpid())
sleep(3)
print("%d 完成操作"%os.getpid())
sem.release() #增加信号量