进程间通信(IPC)之队列

【一】引入

【1】什么是进程间通信(Inter-Process Communication, IPC)

  • 进程间通信(Inter-Process Communication, IPC)是指两个或多个进程之间进行信息交换的过程。

  • 它是一种计算机编程技术,用于在不同进程之间共享数据和资源。

【2】如何实现进程间通信

  • 借助于消息队列,进程可以将消息放入队列中,然后由另一个进程从队列中取出。

  • 这种通信方式是非阻塞的,即发送进程不需要等待接收进程的响应即可继续执行。

  • multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

【3】什么是管道

  • 管道是一种半双工的通信机制,即只能在一个方向上进行数据传输。

  • 子进程可以通过继承父进程的管道来实现通信。

  • stdin、stdout和stderr是Python中的三个内置文件对象,它们分别代表标准输入、标准输出和标准错误。

  • 这些对象也可以作为管道使用。

  • 当我们在一个进程中使用read方法读取管道内的消息后,其他进程将无法再获取该管道内的任何其他消息。

  • 因此,我们需要使用锁或其他同步机制来确保多个进程能够正确地访问和修改共享资源。

【4】什么是队列(管道 + 锁)

  • 队列是一种线程安全的数据结构,它支持在多线程环境中高效地实现生产者-消费者模型。

  • 队列的特性是先进先出(First-In-First-Out, FIFO),即先插入队列的数据将先被取出。

  • 堆栈是一种后进先出(Last-In-First-Out, LIFO)的数据结构,与队列相反,最后插入的数据将首先被取出。

【5】进程间通信的目的

  • 存是为了更好的取

  • 千方百计的存

  • 简单快捷的取

【二】队列介绍

【1】创建队列的类(底层就是以管道和锁定的方式实现)

(1)模块介绍 queue

  • 创建一个共享的队列,大家都通过一个队列操作数据

(2)语法(导入queue模块)

  • from queue import Queue

(3)介绍

  • 得到一个队列对象

  • queue = Queue()

  • maxsize:允许存在的队列最大容量

(4)方法介绍

  • q.put

    • 用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout

    • 如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout 指定的时间,直到该队列有剩余的空间,如果超时,会抛出Queue.Full异常

    • 如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常

  • q.get

    • 可以从队列读取并且删除一个元素,同样,get方法有两个可选参数:timeout和blocked

    • 如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常

    • 如果blocked为False,有两种情况再,如果Queue有一个值可用,则立即返回值,,否则,如果队列为空,则立即抛出Queue.Empty异常

  • q.get_nowait() == q.get(False)

  • q.put_nowait() == q.put(False)

  • q.empty()

    • 调用此方法是q为空则返回True,该结果不可靠,比如,在返回True的过程中队列又加入了项目

  • q.full()

    • 调用此方法是q已满则返回True,该结果不可靠,比如,在返回True的过程中队列中项目被取走

  • q.qsize()

    • 返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

(5)其他方法

  • q.cancel_join_thread()

    • 不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞

  • q.close()

    • 关闭队列,防止队列中加入更多数据。

    • 调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。

    • 如果q被垃圾收集,将调用此方法。

    • 关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。

    • 例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。

  • q.join_thread()

    • 连接队列的后台线程。

    • 此方法用于在调用q.close()方法之后,等待所有队列项被消耗。

    • 默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为

(6)使用

import queue
​
q = queue.Queue(maxsize=3)
q.put(1)
q.put(2)
q.put(3)
# q.put_nowait(4)     # put 的时候如果超出队列最大容量就直接报错 queue.Full
print(q.full())     # 返回当前队列是否填充满,如果满了就会返回True
# q.put(4, timeout=1)      # timeout : 延迟时间,延迟时间抛出异常 q.Full
# q.put(4)      # put 的时候如果超出队列最大容量就会阻塞,直至有一个人取出队列中的一个数据
print(q.get())
print(q.get())
print(q.empty())    # 判断当前队列是够全部为空
print(q.get())
# print(q.get(timeout=1))       # timeout : 延迟时间,延迟时间抛出异常 _queue.Empty
# print(q.get()) # 如果队列中没有数据的时候就会一直阻塞,直至队列中增加新的数据
print(q.get_nowait()) # 如果队列中没有数据的时候就报错 _queue.Empty

【三】队列实现进程间通信

【1】主子通信

# 主子通信
import multiprocessing
import time
import random
​
​
def son_process(name,queue):
    print(f'son_process {name} is starting ...')
    msg = queue.get()
    print(f'son_process {name} recv msg is {msg}')
    print(f'son_process {name} is ending ...')
​
​
def main_process():
    # 【一】创建一个队列对象  --- 用多进程里面的队列模块
    queue = multiprocessing.Queue(5)
    msg = f'from son_process the message is you handsome'
    queue.put(msg)
    task = multiprocessing.Process(target=son_process, kwargs={'name':'son','queue':queue})
​
    task.start()
    task.join()
​
​
if __name__ == '__main__':
    main_process()

【2】子子通信

# 子子通信
from multiprocessing import Process, Queue
import os
​
​
# 子进程和子进程之间进行通信
def producer(queue):
    # 生产者
    # 向队列中方数据
    print(f'this is producer {os.getpid()} starting ...')
    # 向队列中添加数据
    msg = 'this is a message from producer'
    queue.put(msg)
    print(f'this is producer {os.getpid()} ending ... ')
​
​
def customer(queue):
    # 消费者
    print(f'this is customer {os.getpid()} starting ... ')
    msg = queue.get()
    print(f'the message is {msg}')
    print(f'this is customer {os.getpid()} ending ...')
​
​
def main():
    # 【一】得到一个队列对象
    queue = Queue(5)
    # 【二】创建两个多进程对象
    # 【1】生产者子进程
    producer_process = Process(target=producer, args=(queue,))
    # 【2】消费者子进程
    customer_process = Process(target=customer, args=(queue,))
    # 【3】启动子进程
    producer_process.start()
    customer_process.start()
    # 【4】阻塞并行
    producer_process.join()
    customer_process.join()
​
​
if __name__ == '__main__':
    main()
    
    
# this is producer 41920 starting ...
# this is producer 41920 ending ... 
# this is customer 45812 starting ... 
# the message is this is a message from producer
# this is customer 45812 ending ...    

【四】生产者和消费者模型

【1】理论

(1)生产者模型

  • 生产者模型和消费者模型是指通过利用队列解耦生产者和消费者的一种并发编程模型。

  • 在生产者模型中,生产者负责将数据放入共享队列中,而消费者则从队列中取出数据进行处理。

  • 生产者和消费者之间通过共享这个队列来进行信息的交流。

  • 这种模型适用于生产者和消费者之间的处理速度不一致的情况,同时还能够保证数据传输的安全性和正确性。

(2)消费者模型

  • 在消费者模型中,消费者负责向队列中插入任务,而由线程池中的工作线程进行任务的处理。

  • 消费者和工作线程之间通过共享线程池中的任务队列来完成任务分发和执行。

  • 这种模型适用于任务处理需要一定时间的情况,能够充分利用多线程的优势提高系统的并发性能,提高效率。

(3)小结

  • 生产者:生产/制造东西

  • 消费者:消费/处理东西

  • 该模型还需要一个媒介,队列

【2】场景引入

  • 比如做包子是先将包子做好后放在蒸笼(媒介)里面,买包子的去蒸笼里面拿

  • 厨师做菜之后用盘子(媒介)装着给消费者端过去

  • 生产者与消费者之间不是直接做交互的,而是借助于媒介

  • 生产者(做包子的) + 媒介(蒸包子) + 消费者(吃包子的)

【3】模型

import time
import random
from multiprocessing import Process, Queue
​
​
def producer(name,food,queue):
    # 【一】生产者的标志:名字
    # 【二】生产出食物
    # 【三】借助队列将食物扔到队列中
    for i in range(5):
        data = f'当前大厨{name} :>>> 生产出了 第{i}道{food}!'
        sleep_time = random.randint(1,3)
        time.sleep(sleep_time)
        queue.put(data)
​
​
def customer(name,queue):
    while True:
        food = queue.get()
        sleep_time = random.randint(1,3)
        time.sleep(sleep_time)
        data = f"当前顾客{name} :>>> 消费了 {food}!"
        print(data)
​
# 产生问题:
# 生产者生产完所有数据
# 消费者消费完所有数据,但是消费者还在等待生产者生产数据
# 解决思路:
# 在生产者生产完最后一道菜的时候添加标志
# 消费者消费完最后一道菜的时候应该主动结束消费
​
def main():
    # 【一】创建生产者对象
    # 【二】创建队列对象
    queue = Queue(5)
    # 【1】生产者 knight
    p_producer_knight = Process(
        target=producer,
        args=('knight','清炒水芹菜',queue)
    )
    # 【2】生产者 ddt
    p_producer_ddt = Process(
        target=producer,
        args=('ddt','可乐鸡翅',queue)
    )
    # 【3】消费者 gdy
    p_consumer_gdy = Process(
        target=customer,
        args=('ddt', queue)
    )
​
    # 【4】消费者 hyt
    p_consumer_hyt = Process(
        target=customer,
        args=('hyt', queue)
    )
​
    p_producer_knight.start()
    p_producer_ddt.start()
    p_consumer_hyt.start()
    p_consumer_gdy.start()
​
    p_producer_knight.join()
    p_producer_ddt.join()
    p_consumer_hyt.join()
    p_consumer_gdy.join()
import time
import random
from multiprocessing import Process, Queue
​
​
def producer(name, food, queue):
    # 【一】生产者的标志 : 名字
    # 【二】生产出食物
    # 【三】借助队列将食物扔到队列中
    for i in range(5):
        data = f'当前大厨{name} :>>> 生产出了 第{i}道{food}!'
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)
        queue.put(data)
    queue.put('q')
​
​
def customer(name, queue):
    while True:
        food = queue.get()
        if food == 'q':
            data = f'当前顾客{name} :>>>店铺已打样!'
            print(data)
            break
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)
        data = f'当前顾客{name} :>>> 消费了 {food}!'
        print(data)
​
​
# 产生问题:
# 生产者生产完所有数据
# 消费者消费完所有数据,但是消费者还在等待生产者生产数据
# 解决思路:
# 在生产者生产完最后一道菜的时候添加标志
# 消费者消费完最后一道菜的时候应该主动结束消费
def main():
    # 【一】创建生产者对象
    # 【二】创建队列对象
    queue = Queue(5)
    # 【1】生产者 dream
    p_producer_dream = Process(
        target=producer,
        args=('dream', '鱼香肉丝', queue)
    )
    # 【2】生产者 hope
    p_producer_hope = Process(
        target=producer,
        args=('hope', '宫保鸡丁', queue)
    )
    # 【3】消费者 opp
    p_producer_opp = Process(
        target=customer,
        args=('opp', queue)
    )
    # 【4】消费者 ooo
    p_producer_ooo = Process(
        target=customer,
        args=('ooo', queue)
    )
​
    p_producer_dream.start()
    p_producer_hope.start()
    p_producer_opp.start()
    p_producer_ooo.start()
​
    p_producer_dream.join()
    p_producer_hope.join()
    p_producer_opp.join()
    p_producer_ooo.join()

借助别写好的模块完成标志的增加

from multiprocessing import JoinableQueue, Process
import random
import time
​
​
def producer(name, food, queue):
    # 【一】生产者的标志:名字
    # 【二】生产出食物
    # 【三】借助队列将食物扔到队列中
    for i in range(5):
        data = f'当前大厨{name} :>>> 生产出了 第{i}道{food}!'
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)
        print(data)
        queue.put(data)
    # 结束标志自己主动加 q
    # queue.put('q')
    # 只需要借助模块的方法
    queue.join()
​
​
def customer(name, queue):
    while True:
        food = queue.get()
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)
        data = f'当前顾客{name} :>>> 消费了 {food}!'
        print(data)
        # 当生产者生产完所有数据后会增加 join 标志 ---> 阻塞
        # task_done 这个方法接收到 自己主动结束消费
        queue.task_done()  # 0
​
​
# 生产者和消费者的子进程都要启动
# 生产者会等待所有生产的子进程结束后再结束
# 消费者子进程一直在跑 不给消费者加join
# 给消费者子进程增加守护进程 --> 会随着主进程死掉而死掉
def main():
    # 【一】创建生产者对象
    # 【二】创建队列对象
    queue = JoinableQueue(5)
    # 【1】生产者 knight
    p_producer_knight = Process(target=producer,
                                args=('knight', '芹菜炒肉丝', queue)
                                )
    # 【2】生产者 ddt
    p_producer_ddt = Process(target=producer,
                             args=('ddt', '香辣虾', queue)
                             )
    # 【3】消费者 hyt
    p_customer_hyt = Process(
        target=customer,
        args=('hyt', queue)
    )
​
    # 【3】消费者 gdy
    p_customer_gdy = Process(
        target=customer,
        args=('gdy', queue)
    )
​
    p_customer_hyt.daemon = True
    p_customer_gdy.daemon = True
​
    # 只需要启动生产者生产数据
    # 生产者生产结束 加了一个标志,告诉消费者 生产结束
    # 消费者task_done 标志知道生产者没有东西了,主动结束
    p_producer_knight.start()
    p_producer_ddt.start()
    p_customer_hyt.start()
    p_customer_gdy.start()
​
    p_producer_knight.join()
    p_producer_ddt.join()
​
​
if __name__ == '__main__':
    main()
当前大厨ddt :>>> 生产出了 第0道香辣虾!
当前大厨knight :>>> 生产出了 第0道芹菜炒肉丝!
当前顾客hyt :>>> 消费了 当前大厨ddt :>>> 生产出了 第0道香辣虾!!
当前大厨ddt :>>> 生产出了 第1道香辣虾!
当前大厨knight :>>> 生产出了 第1道芹菜炒肉丝!
当前顾客gdy :>>> 消费了 当前大厨knight :>>> 生产出了 第0道芹菜炒肉丝!!
​
当前顾客hyt :>>> 消费了 当前大厨ddt :>>> 生产出了 第1道香辣虾!!
当前大厨ddt :>>> 生产出了 第2道香辣虾!
当前大厨knight :>>> 生产出了 第2道芹菜炒肉丝!
当前顾客gdy :>>> 消费了 当前大厨knight :>>> 生产出了 第1道芹菜炒肉丝!!
当前大厨ddt :>>> 生产出了 第3道香辣虾!
当前顾客hyt :>>> 消费了 当前大厨ddt :>>> 生产出了 第2道香辣虾!!当前大厨ddt :>>> 生产出了 第4道香辣虾!
​
当前大厨knight :>>> 生产出了 第3道芹菜炒肉丝!
当前顾客hyt :>>> 消费了 当前大厨ddt :>>> 生产出了 第3道香辣虾!!
当前顾客gdy :>>> 消费了 当前大厨knight :>>> 生产出了 第2道芹菜炒肉丝!!
当前大厨knight :>>> 生产出了 第4道芹菜炒肉丝!
当前顾客gdy :>>> 消费了 当前大厨knight :>>> 生产出了 第3道芹菜炒肉丝!!
当前顾客hyt :>>> 消费了 当前大厨ddt :>>> 生产出了 第4道香辣虾!!
当前顾客gdy :>>> 消费了 当前大厨knight :>>> 生产出了 第4道芹菜炒肉丝!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值