进程间通信
【一】什么是进程间通信
- 进程间通信(Inter-Process Communication, IPC)是指两个或多个进程之间进行信息交换的过程。
- 它是一种计算机编程技术,用于在不同进程之间共享数据和资源。
【二】如何实现进程间通信
- 借助于消息队列,进程可以将消息放入队列中,然后由另一个进程从队列中取出。
- 这种通信方式是非阻塞的,即发送进程不需要等待接收进程的响应即可继续执行。
- multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
【三】进程间通信的目的
【四】进程间通信(IPC机制)
【1】什么是IPC机制
- IPC机制指进程间通信机制(Inter-Process Communication),它是指在不同进程间传输数据或者信息的一种机制。
- 在多进程操作系统中,各个进程相互独立,不能直接访问对方的内存空间,所以必须通过特殊的通信方式实现进程之间的信息交换和协调。
- 常见的IPC机制包括管道、消息队列、共享内存和信号量等方式。
- 其中,管道、消息队列和共享内存都是用于进程之间的数据传输
- 而信号量则是用于进程之间的同步与互斥。
- 通过使用IPC机制,可以使得不同进程能够有效地协同工作,在复杂的程序设计中起到十分重要的作用。
【2】子进程与主进程之间通过队列进行通信
from multiprocessing import Process, Queue
import time
def producer(q):
# 子进程将主进程传入的数据取出
print(f"{q.get()}")
q.put("这是子进程发送给主进程的消息!")
if __name__ == '__main__':
q = Queue(5)
task = Process(target=producer, args=(q,))
task.start()
q.put("这是主进程!")
time.sleep(1)
# 主进程无法向子进程发送消息 , 在上面发送的是空的 队列 ,要想使用子进程的前提是 子进程启动
print(f"这是来自子进程的消息 :>>>> {q.get()}")
task.join()
# 这是主进程!
# 这是来自子进程的消息 :>>>> 这是子进程发送给主进程的消息!
【3】子进程与子进程之间借助队列进行通信
from multiprocessing import Process, Queue
def producer(q):
# 子进程将主进程传入的数据取出
q.put("这是来自生产者的消息!")
def customer(q):
msg = q.get()
print(f'这是消费者取出的数据 :>>>> {msg}')
if __name__ == '__main__':
q = Queue(5)
# 【1】创建两个进程对象
task_producer = Process(target=producer, args=(q,))
task_customer = Process(target=customer, args=(q,))
# 【2】启动子进程
task_producer.start()
task_customer.start()
# 【3】等待进程结束
task_producer.join()
task_customer.join()
【五】队列
【1】什么是队列(管道 + 锁)
- 队列是一种线程安全的数据结构,它支持在多线程环境中高效地实现生产者-消费者模型。
- 队列的特性是先进先出(First-In-First-Out, FIFO),即先插入队列的数据将先被取出。
- 堆栈是一种后进先出(Last-In-First-Out, LIFO)的数据结构,与队列相反,最后插入的数据将首先被取出。
【2】创建队列的类(底层就是以管道和锁定的方式实现)
import queue
q = queue.Queue(maxsize)
- Queue([maxsize]):
- 创建共享的进程队列
- Queue是多进程安全的队列
- 可以使用Queue实现多进程之间的数据传递。
【2】主要方法
(1)q.pet():
- 用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。
- 如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常
- 如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
import queue
q = queue.Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
q.put(6, timeout=1) # queue.Full
(2)q.get():队列无数据不报错
- 可以从队列读取并且删除一个元素,同样,get方法有两个可选参数:blocked和timeout。
- 如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。
- 如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
import queue
q = queue.Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
a = q.get()
print(a) # 1
b = q.get()
print(b) # 2
(3)q.get_nowait():队列无数据报错
- 同q.get(False)
- 当队列中的数据已经被取空后,使用 get_nowait() 会触发报错
import queue
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
a = q.get()
print(a) # 1
b = q.get()
print(b) # 2
c = q.get_nowait() # 3
print(c)
d = q.get_nowait() # 3
print(d) # raise Empty : _queue.Empty
(4)q.full():判断当前队列数据是否填满队列
- 调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
import queue
q = queue.Queue(2)
q.put(1)
print(q.full()) # False
q.put(2)
print(q.full()) # True
(5)q.empty():判断当前队列是否为空
- 调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
import queue
q = queue.Queue(2)
# 向队列中放数据
q.put(1)
q.put(2)
print(q.empty()) # False
# 在队列中取数据
q.get()
print(q.empty()) # False
q.get()
print(q.empty()) # True
(6)小结
# 导入模块
import queue
# 创建队列桶
q = queue.Queue(指定桶的容量)
# 向队列中添加数据
q.put(放入的数据类型)
# 判断当前队列是否满了,满了返回 True 否则为 False
q.full()
# 从队列中取出数据,队列中无数据不会报错,但是会夯住
data = q.get()
# 从队列中取出数据,队列中无数据会在指定延迟时间后抛出异常:raise Empty : _queue.Empty
data = q.get(timeout=秒数)
# 从队列中取出数据,队列中无数据会抛出异常:raise Empty : _queue.Empty
data = q.get_nowait()
# 判断当前队列是够空了,空了返回 True 否则为 False
q.empty()
【六】管道
【1】创建管道的类
-
Pipe([duplex])
- 在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象
- 强调一点:必须在产生Process对象之前产生管道
【2】用法
from multiprocessing import Pipe
left_conn,right_conn = Pipe()
right_conn.close()
left_conn.close()
left_conn.close()
right_conn.close()
【3】管道通信特别注意
- 生产者和消费者都没有使用管道的某个端点,就应该将其关闭,
- 如在生产者中关闭管道的右端,在消费者中关闭管道的左端。
- 如果忘记执行这些步骤,程序可能再消费者中的recv()操作上挂起。
- 管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生产EOFError异常。
- 因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。
【七】生产者和消费者模型
【1】理论
(1)生产者模型
- 生产者模型和消费者模型是指通过利用队列解耦生产者和消费者的一种并发编程模型。
- 在生产者模型中,生产者负责将数据放入共享队列中,而消费者则从队列中取出数据进行处理。
- 生产者和消费者之间通过共享这个队列来进行信息的交流。
- 这种模型适用于生产者和消费者之间的处理速度不一致的情况,同时还能够保证数据传输的安全性和正确性。
(2)消费者模型
- 在消费者模型中,消费者负责向队列中插入任务,而由线程池中的工作线程进行任务的处理。
- 消费者和工作线程之间通过共享线程池中的任务队列来完成任务分发和执行。
- 这种模型适用于任务处理需要一定时间的情况,能够充分利用多线程的优势提高系统的并发性能,提高效率。
(3)小结
- 生产者:生产/制造东西
- 消费者:消费/处理东西
- 该模型还需要一个媒介
【2】场景引入
- 比如做包子是先将包子做好后放在蒸笼(媒介)里面,买包子的去蒸笼里面拿
- 厨师做菜之后用盘子(媒介)装着给消费者端过去
- 生产者与消费者之间不是直接做交互的,而是借助于媒介
- 生产者(做包子的) + 媒介(蒸包子) + 消费者(吃包子的)
【3】消费者大于生产者
from multiprocessing import Process, Queue
import time
# 生产者函数:做包子
def producer(name, food, queue_obj):
for i in range(1, 5):
data = f"当前大厨 :>>>> {name} | 生产出了第 {i} 道 {food}!"
time.sleep(1)
# 将生产出来的数据放入媒介
queue_obj.put(data)
queue_obj.put([food,None])
def customer(name, queue_obj):
while True:
food = queue_obj.get()
if food[1] is None:
print(f'消费者将{food[0]}消费完毕:>>>!!')
break
time.sleep(1)
print(f'当前消费者 :>>> {name} | 消费了 :>>>> {food}')
def main():
task_list = []
# 【1】创建队列对象
queue_obj = Queue()
# 【2】创建消费者对象 (厨师)
producer_qcc = Process(target=producer, args=('qcc', '宫保鸡丁', queue_obj))
producer_tom = Process(target=producer, args=('tom', '鱼香肉丝', queue_obj))
# 【3】创建消费者对象 (顾客)
customer_zhangs = Process(target=customer, args=('zhangs', queue_obj))
customer_lisi = Process(target=customer, args=('lisi', queue_obj))
task_list.append(producer_qcc)
task_list.append(producer_tom)
task_list.append(customer_zhangs)
task_list.append(customer_lisi)
# 【4】启动
[task.start() for task in task_list]
[task.join() for task in task_list]
if __name__ == '__main__':
main()
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 1 道 鱼香肉丝!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 1 道 宫保鸡丁!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 2 道 宫保鸡丁!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 2 道 鱼香肉丝!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 3 道 鱼香肉丝!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 3 道 宫保鸡丁!
# 消费者将鱼香肉丝消费完毕:>>>!!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 4 道 鱼香肉丝!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 4 道 宫保鸡丁!
# 消费者将宫保鸡丁消费完毕:>>>!!
【4】JoinableQueue模块
from multiprocessing import Process, Queue,JoinableQueue
import time
def producer(name, food, queue_obj):
for i in range(1, 5):
data = f"当前大厨 :>>>> {name} | 生产出了第 {i} 道 {food}!"
time.sleep(1)
queue_obj.put(data)
def customer(name, queue_obj):
while True:
food = queue_obj.get()
print(f'当前消费者 :>>> {name} | 消费了 :>>>> {food}')
queue_obj.task_done()
def main():
task_list = []
# 【1】创建队列对象
queue_obj = JoinableQueue()
# 【2】创建消费者对象 (厨师)
producer_qcc = Process(target=producer, args=('qcc', '宫保鸡丁', queue_obj))
producer_tom = Process(target=producer, args=('tom', '鱼香肉丝', queue_obj))
# 【3】创建消费者对象 (顾客)
customer_zhangs = Process(target=customer, args=('zhangs', queue_obj))
customer_lisi = Process(target=customer, args=('lisi', queue_obj))
task_list.append(producer_qcc)
task_list.append(producer_tom)
task_list.append(customer_zhangs)
task_list.append(customer_lisi)
customer_zhangs.daemon=True
customer_lisi.daemon = True
[task.start() for task in task_list]
producer_qcc.join()
producer_tom.join()
print('消费者消费完毕')
if __name__ == '__main__':
main()
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 1 道 鱼香肉丝!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 1 道 宫保鸡丁!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 2 道 宫保鸡丁!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 2 道 鱼香肉丝!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 3 道 鱼香肉丝!
# 当前消费者 :>>> lisi | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 3 道 宫保鸡丁!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> tom | 生产出了第 4 道 鱼香肉丝!
# 当前消费者 :>>> zhangsan | 消费了 :>>>> 当前大厨 :>>>> qcc | 生产出了第 4 道 宫保鸡丁!
# 消费者消费完毕