Python学习笔记--并发编程之进程间通信(Queue模块)

Queue模块是multiprocessing包下面的一个方法,返回的是一个queue对象。queue是一种先进先出的队列结构。

Python中的queue对象就是通过一个进程将消息插队到队尾,另一个进程从队列的头部进行数据读取,以此实现进程间的通信。queue对象的方法为multiprocessing.Queue([maxsize]),返回一个被进程共享的queue对象,maxsize是一个可选参数,表示这个队列的大小。

敲黑板:

Python在queue的内部实现中,使用了管道和一些同步机制(互斥锁,信号槽),queue对象为进程共享,进程向队列中写入数据时,queue对象会启动一个feeder线程,feeder线程将缓存中的数据写入管道中。

queue对象的常用操作方法如下:

(1)queue.qsize() :返回队列的长度。

(2)queue.empty() :返回布尔值类型,用于判断队列是否是空的。True表示空,否则非空。

(3)queue.fill():返回值为布尔值,True表示队列是满的,否则表示不满。

(4)queue.put(item, block=True, timeout=None) ,其中,item是要添加到队列中的元素。block参数指定是否阻塞线程,如果队列已满,则线程被阻塞,直到队列中有空闲位置。timeout参数指定阻塞的超时时间,如果超时时间到了仍然无法添加到队列中,则抛出Full异常。

(5)queue.put_nowait(item),相当于queue.put(item,False)

(6)queue.get(item, block=True, timeout=None) ,从列表中读数据

(7)queue.get_nowait():相当于queue.get(item,False)

(8)queue.close() :关闭队列对象,当前进程将不会再向队列中写入数据

下面是一个完整的编程实例,代码如下:

from multiprocessing import Process,Queue
import time
import random
import os

def producer(Q):
    current_pid = os.getpid()
    for _ in range(5):
        Q.put({"process_pid":current_pid,"number":random.randint(0,1000)})
    Q.put(None)
    # 关闭queue队列对象,当前进程不再向队列中写入数据
    Q.close()

def consumer(Q):
    current_pid = os.getpid()
    while True:
        message = Q.get()
        if message:
            print('子进程{}收到了来自子进程{}的随机数据:{}'.format(current_pid,message['process_pid'],message['number']))
        else:
            break
    Q.close()

if __name__ == '__main__':
    Q = Queue()

    producer_process = Process(target=producer,args=(Q,))
    consumer_process = Process(target=consumer,args=(Q,))

    producer_process.start()
    consumer_process.start()

    producer_process.join()
    consumer_process.join()

    Q.close

运行结果如下图:

思考1:

执行上面代码,两个子进程并行,如果生产者子进程执行的函数中,我延时5秒,那么生产者子进程并没有来及把数据写入到队列Q对象中,那么消费者子进程就是不是取不到数据了?修改代码如下:

def producer(Q):
    #延时5秒
    time.sleep(5)

    current_pid = os.getpid()
    for _ in range(5):
        Q.put({"process_pid":current_pid,"number":random.randint(0,1000)})    
    Q.put(None)
    Q.close()

发现,如上修改,运行结果完全一样。问询文言一心,答复如下:

这是因为生产者进程和消费者进程是并发执行的,它们之间的执行顺序并不是严格的交替进行,而是由操作系统的进程调度器来控制的。

当你在生产者进程的函数中先休眠几秒,消费者进程在这段时间内可能会尝试从队列中取数据,但是此时队列为空,所以消费者进程会被阻塞,直到生产者进程将数据放入队列。

过了几秒后,生产者进程将数据放入队列,这时候消费者进程就会从阻塞状态中被唤醒,并立即从队列中取出数据进行处理。因此,你就能看到消费者进程在生产者进程放入数据后很快就取出了数据。

总的来说,生产者和消费者进程之间的同步和通信是通过队列来实现的。生产者进程将数据放入队列,而消费者进程从队列中取出数据进行处理。当队列为空时,消费者进程会被阻塞,直到生产者进程放入新的数据。这种机制能够确保生产者和消费者之间的数据同步和协调。

思考2:

把生产者子进程执行的函数再修改,放入队列中一个数据后,延迟5秒,那么消费者子进程可以读取到一条信息,是不是这样的哪?并不是的,而是延时5秒后,会继续读取完生产者写进去的5条所有信息(直到最后收到生产者子进程写进去的一条None消息),这是为何?咨询文言一心,答复如下:

当队列数据为空时,消费者子进程会被阻塞,直到队列中有数据为止。一旦队列中有数据,消费者进程就会恢复执行。 这不是基于队列中有一个数据项就恢复,而是只要队列不为空,消费者进程就会恢复执行并取出数据。这种机制确保了生产者和消费者之间的同步,避免了消费者进程在队列为空时无效地轮询资源,提高了整体效率。

当消费者子进程执行队列的.get()方法时,如果队列为空,该子进程就会被阻塞,直到队列中有新的数据可供获取。这是多进程队列的一种典型行为,用于同步生产者和消费者的操作。

通过以上2个思考,两个子进程共享queue队列对象,一个写入,一个读取,读取的子进程的.get()方法,如果发现队列为空,则让读取的子进程阻塞,等待写的子进程写入后,会从阻塞到就绪态及运行,在读取的函数中,一旦为空,就进入了阻塞态,直到读取到了写入的None(特定)消息,结束读取,才会结束,从而确保能够读取完写入的所有消息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值