进程间通信

【一】进程间通信

(1)什么是进程间通信

  • 进程间通信(Inter-Process Communication,IPC)是指在不同的进程之间传递数据或信息的机制。

(2)如何实现进程间通信

  • 借助于消息队列,进程可以将消息放入队列中,然后由另一个进程从队列中取出。
  • 这种通信方式是非阻塞的,即发送进程不需要等待接收进程的响应即可继续执行。
  • multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

(3)什么是管道

  • 管道是一种半双工的通信机制,即只能在一个方向上进行数据传输。
  • 子进程可以通过继承父进程的管道来实现通信。
  • stdin、stdout和stderr是Python中的三个内置文件对象,它们分别代表标准输入、标准输出和标准错误。
  • 这些对象也可以作为管道使用。
  • 当我们在一个进程中使用read方法读取管道内的消息后,其他进程将无法再获取该管道内的任何其他消息。
  • 因此,我们需要使用锁或其他同步机制来确保多个进程能够正确地访问和修改共享资源。

(4)什么是队列

  • 队列是一种线程安全的数据结构,它支持在多线程环境中高效地实现生产者-消费者模型。
  • 队列的特性是先进先出(First-In-First-Out, FIFO),即先插入队列的数据将先被取出。
  • 堆栈是一种后进先出(Last-In-First-Out, LIFO)的数据结构,与队列相反,最后插入的数据将首先被取出。

(5)进程间通信的目的

  • 存是为了更好的取
  • 千方百计的存
  • 简单快捷的取

(6)子进程与主进程之间通信

  • 由于主进程和子进程之间是独立运行的,各自拥有独立的内存空间,它们在往队列中放入消息时,并不会相互影响。
from multiprocessing import Process, Queue
# 创建一个子进程函数
def producer(queue_obj):
queue_obj.put('我是子进程,嘿嘿')
if __name__ == '__main__':
queue_obj = Queue(5)
# 主进程无法向子进程发送消息
# queue_obj.put('我是主进程,嘿嘿')
p = Process(target=producer,args=(queue_obj,))
p.start()
p.join()
print(queue_obj.get()) # 我是子进程,嘿嘿

(7)子进程与子进程之间通信

  • 通过queue实现了子进程和子进程之间的通信,其中主进程通过队列向子进程发送了一条消息,子进程接收并打印了这条消息。
from multiprocessing import Process, Queue
def boss(queue_obj):
# 子进程将主进程传入的数据取出
queue_obj.put("好好上班!")
def employee(queue_obj):
msg = queue_obj.get()
print(f'老板发来一条消息:>>>{msg}') # 老板发来一条消息:>>>好好上班!
if __name__ == '__main__':
queue_obj = Queue(5)
# 【1】创建两个进程对象
task_boss = Process(target=boss, args=(queue_obj,))
task_employee = Process(target=employee, args=(queue_obj,))
# 【2】启动子进程
task_boss.start()
task_employee.start()
# 【3】等待进程结束
task_boss.join()
task_employee.join()

(8)生产者和消费者模型

  • 生产者-消费者模型是一种常见的并发编程模型,其中有两类并发执行的任务:生产者和消费者。生产者负责生成数据,而消费者负责处理和消耗这些数据。它们通过共享的缓冲区(通常是一个队列)进行通信,以实现解耦和异步操作。

(1)消费者大于生产者

  • 消费者数据没有被卡住
from multiprocessing import Process, Queue
import random
import time
def producer(name, food, queue_obj):
for i in range(1, 5):
data = f"厨师{name} 生产出了第{i}{food}"
time.sleep(random.randint(1, 3))
# 将生产出来的数据放入媒介
queue_obj.put(data)
def customer(name, queue_obj):
while True:
from_producer_food = queue_obj.get()
time.sleep(random.randint(1, 2))
print(f"客人 {name} 吃了{from_producer_food}")
def main():
task_list = []
# 【1】创建队列对象
queue_obj = Queue()
# 【2】创建生产者对象 (厨师)
producer_heart = Process(target=producer, args=('heart', '小炒肉', queue_obj))
producer_god = Process(target=producer, args=('god', '酸菜鱼', queue_obj))
# 【3】创建消费者对象 (客人)
customer_pp = Process(target=customer, args=('pp', queue_obj))
customer_godfather = Process(target=customer, args=('godfather', queue_obj))
task_list.append(producer_heart)
task_list.append(producer_god)
task_list.append(customer_pp)
task_list.append(customer_godfather)
# 【4】启动
task_list_new = []
for task in task_list:
task.start()
task_list_new.append(task)
for task in task_list_new:
task.join()
if __name__ == '__main__':
main()
# 客人 pp 吃了厨师god 生产出了第1道 酸菜鱼
# 客人 godfather 吃了厨师god 生产出了第2道 酸菜鱼
# 客人 pp 吃了厨师heart 生产出了第1道 小炒肉
# 客人 godfather 吃了厨师heart 生产出了第2道 小炒肉
# 客人 godfather 吃了厨师god 生产出了第3道 酸菜鱼
# 客人 pp 吃了厨师heart 生产出了第3道 小炒肉
# 客人 godfather 吃了厨师heart 生产出了第4道 小炒肉
# 客人 pp 吃了厨师god 生产出了第4道 酸菜鱼
# 卡住了

(2)为生产者添加结束标志

  • 正常结束
from multiprocessing import Process, Queue
import random
import time
def producer(name, food, queue_obj):
for i in range(1, 5):
data = f"厨师{name} 生产出了第{i}{food}"
time.sleep(random.randint(1, 3))
# 将生产出来的数据放入媒介
queue_obj.put(data)
# 放一个结束标志
queue_obj.put('q')
def customer(name, queue_obj):
while True:
from_producer_food = queue_obj.get()
if from_producer_food == 'q':
break
time.sleep(random.randint(1, 2))
print(f"客人 {name} 吃了{from_producer_food}")
def main():
task_list = []
# 【1】创建队列对象
queue_obj = Queue()
# 【2】创建生产者对象 (厨师)
producer_heart = Process(target=producer, args=('heart', '小炒肉', queue_obj))
producer_god = Process(target=producer, args=('god', '酸菜鱼', queue_obj))
# 【3】创建消费者对象 (客人)
customer_pp = Process(target=customer, args=('pp', queue_obj))
customer_godfather = Process(target=customer, args=('godfather', queue_obj))
task_list.append(producer_heart)
task_list.append(producer_god)
task_list.append(customer_pp)
task_list.append(customer_godfather)
# 【4】启动
task_list_new = []
for task in task_list:
task.start()
task_list_new.append(task)
for task in task_list_new:
task.join()
if __name__ == '__main__':
main()
# 客人 godfather 吃了厨师heart 生产出了第1道 小炒肉
# 客人 pp 吃了厨师god 生产出了第1道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第2道 小炒肉
# 客人 pp 吃了厨师god 生产出了第2道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第3道 小炒肉
# 客人 pp 吃了厨师god 生产出了第3道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第4道 小炒肉
# 客人 pp 吃了厨师god 生产出了第4道 酸菜鱼

(3)JoinableQueue模块

  • 每当向队列对象中存入数据的时候,队列对象内部会有一个计数器 +1
  • 每当调用一次 task_done() 方法的时候,队列对象内部的计数器 -1
  • join() 当计数器为 0 时,继续执行代码
  • 很好的解决了进程结束的问题task_done()
from multiprocessing import Process, Queue, JoinableQueue
import random
import time
def producer(name, food, queue_obj):
for i in range(1, 5):
data = f"厨师{name} 生产出了第{i}{food}"
time.sleep(random.randint(1, 3))
# 将生产出来的数据放入媒介
queue_obj.put(data)
def customer(name, queue_obj):
while True:
from_producer_food = queue_obj.get()
time.sleep(random.randint(1, 2))
print(f"客人 {name} 吃了{from_producer_food}")
queue_obj.task_done()
def main():
# 【1】创建队列对象
queue_obj = JoinableQueue()
# 【2】创建生产者对象 (厨师)
producer_heart = Process(target=producer, args=('heart', '小炒肉', queue_obj))
producer_god = Process(target=producer, args=('god', '酸菜鱼', queue_obj))
# 【3】创建消费者对象 (客人)
customer_pp = Process(target=customer, args=('pp', queue_obj))
customer_godfather = Process(target=customer, args=('godfather', queue_obj))
producer_heart.start()
producer_god.start()
# 将消费者设置成守护进程:主进程死亡,子进程跟着死亡
customer_pp.daemon = True
customer_godfather.daemon = True
customer_pp.start()
customer_godfather.start()
producer_heart.join()
producer_god.join()
queue_obj.join()
if __name__ == '__main__':
main()
# 客人 pp 吃了厨师god 生产出了第1道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第1道 小炒肉
# 客人 pp 吃了厨师god 生产出了第2道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第2道 小炒肉
# 客人 pp 吃了厨师god 生产出了第3道 酸菜鱼
# 客人 godfather 吃了厨师heart 生产出了第3道 小炒肉
# 客人 pp 吃了厨师heart 生产出了第4道 小炒肉
# 客人 godfather 吃了厨师god 生产出了第4道 酸菜鱼

【二】队列模块queue

(1)介绍

  • 在Python中,queue 模块提供了用于实现队列的类,是多线程和多进程编程中常用的工具之一。队列是一种先进先出的数据结构,即最先进入队列的元素最先被取出。

(2)用法

这段代码使用了Python的 queue 模块,创建了一个大小为5的队列。以下是对代码的逐行解释:

  1. import queue: 引入Python标准库中的 queue 模块。
  2. q = queue.Queue(maxsize=5): 创建了一个最大容量为5的队列对象 q。这是一个标准的先进先出队列,如果队列已满,put() 操作会阻塞,直到有空间为止。
  3. 先向队列中依次放入了5个元素,即1、2、3、4、5。由于队列的最大容量是5,这时队列已经满了。
  4. q.put(6): 这一行是一个注释,表示对于队列已满的情况,如果尝试再放入一个元素(例如6),put() 操作会阻塞,直到队列中有空间为止。
  5. 然后依次从队列中取出了5个元素,并打印出来。由于队列是先进先出,因此输出顺序是1、2、3、4、5。
  6. q.put(6): 将元素6放入队列。由于之前已经取出了5个元素,队列此时有一个空间,因此放入6不会阻塞。
  7. print(q.get()): 从队列中取出元素6,并打印出来。

这段代码主要演示了队列的基本操作,包括放入元素 (put()) 和取出元素 (get())。队列的容量控制了可以放入的元素数量,当队列满时,put() 操作会阻塞,当队列空时,get() 操作也会阻塞。

import queue
q = queue.Queue(maxsize=5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
# q.put(6)
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
q.put(6)
print(q.get())

(3)其他方法

(1)put()

  • put(item, block=True, timeout=None)

  • item: 要放入队列的元素。

  • block: 如果队列已满,是否阻塞等待队列有空间。默认为 True,即阻塞等待。

  • timeout: 如果设置了 block=True,指定阻塞等待的最大时间(秒)。如果超过此时间仍然无法放入元素,则抛出 queue.Full 异常。

import queue
q = queue.Queue(maxsize=5)
q.put(1)

(2)get()

  • get(block=True, timeout=None)

  • block: 如果队列为空,是否阻塞等待队列有元素。默认为 True,即阻塞等待。

  • timeout: 如果设置了 block=True,指定阻塞等待的最大时间(秒)。如果超过此时间仍然无法取出元素,则抛出 queue.Empty 异常。

import queue
q = queue.Queue(maxsize=5)
q.put(1)
print(q.get())

(3)get_nowait()

  • get_nowait() 方法是 Queue 类的一个方法,用于从队列中获取一个元素,但不等待。如果队列为空,它会立马引发 queue.Empty 异常。与 get() 方法不同,get_nowait() 不会阻塞等待队列中有元素可供获取。
import queue
q = queue.Queue(maxsize=3)
q.put(1)
q.put(2)
q.put(6)
print(q.get_nowait()) # 1
print(q.get()) # 2
print(q.get()) # 6
print(q.get_nowait())
# raise Empty
# 如果管道中没有数据了会立马报错

(4)full()

  • full() 方法用于检查队列是否已满。如果队列的元素数量达到了设定的最大容量(通过 maxsize 参数设置),则 full() 方法返回 True;否则返回 False
import queue
q = queue.Queue(maxsize=3)
q.put(1)
q.put(2)
q.put(6)
print(q.full()) # True
print(q.get()) # 1
print(q.get()) # 2
print(q.get()) # 6
print(q.full()) # False

(5)empty()

  • empty() 方法用于检查队列是否为空。如果队列中没有元素,则 empty() 方法返回 True;否则返回 False
import queue
q = queue.Queue(maxsize=3)
q.put(1)
q.put(2)
q.put(6)
print(q.empty()) # False
print(q.get()) # 1
print(q.get()) # 2
print(q.get()) # 6
print(q.empty()) # True
posted @   ssrheart  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示