进程队列

队列是完全数据安全的,唯一一个无论怎么操作都是数据安全的。其他都做不到。永远不会有两个进程同时取一个数据,也不会两个进程同时放值时放丢了。
基于管道 + 锁实现的
基于队列实现了生产者消费者模型

class Queue(object):
    def __init__(self, maxsize=-1):
        self._maxsize = maxsize

    def qsize(self):
        return 0

    def empty(self):
        return False

    def full(self):
        return False

    def put(self, obj, block=True, timeout=None):
        pass

    def put_nowait(self, obj):
        pass

    def get(self, block=True, timeout=None):
        pass

    def get_nowait(self):
        pass

    def close(self):
        pass

    def join_thread(self):
        pass

    def cancel_join_thread(self):
        pass

队列基本方法q.put(), print(q.get():

import queue, time
import multiprocessing
def foo(q):
    time.sleep(1)
    print('son process Queue id', id(q))  # 打印子进程中共享队列的内存地址
    q.put(123)  # 在子进程中的队列放值
    q.put('laura')
if __name__ == '__main__':
    # q = queue.Queue()  # 线程中的共享队列
    q = multiprocessing.Queue()  #  进程中的共享队列
    p = multiprocessing.Process(target=foo, args=(q,))  # 子进程
    p.start()
    p.join()
    print('main process Queue id', id(q))
    print(q.get())  # 主进程取子进程中队列的值
    print(q.get())

运行结果

son process Queue id 2820617612928
main process Queue id 2088486686848
123
laura

由于q.get() 是一个阻塞事件,就是说如果是个空队列的话,q.get()会一直等,不能做以下其他事情。但是如果当队列中没有值也想做其他事情。就要用到非阻塞的方法print(q.get_nowait()).特点:如果队列有值,会相当于q.get一样取值。如果队列里没有值,会报queue.Empty异常。此异常是自带的queue模块中的异常。就是取不出就报错。
q.put_nowait(): 假设队列长度为2,当put3个元素时,会阻塞。当某个进程取走一个值时才能放进去。但有时没有进程来取。就一直会停住了。放不进就报错。0如果用q.put_nowait()会报错queue.Full. 而且数据也会丢。所以一般put_nowait()用的比较少,因为宁愿报错也不宁愿数据丢失
print(q.empty()) # 判断当前队列是否为空,此方法在多进程下不准。
print(q.full()) # 判断当前队列是否满了,此方法在多进程下不准。

from multiprocessing import Process, Queue
def consume(q):
    print(q.get())   # 一般情况下一个进程只取,一个进程只放。

if __name__ == '__main__':
    q = Queue()
    p = Process(target=consume, args=(q,))
    p.start()
    q.put({'123': 123})   # 进程间队列通信的内部机制是socket和pickle一起实现的。所以python中所有数据类型都可以传了# 主进程给队列中放一个字典。

运行结果

{'123': 123}

ps源码中关于进程队列的解释:

进程中的队列是导入multiprocessing,使用Queue类  import multiprocessing
 class multiprocessing.Queue([ maxsize ] )
返回使用管道和一些锁/信号量实现的进程共享队列。当进程首先将项目放入队列时,将启动进给器线程,该对象线程将对象从缓冲区传输到管道中。

标准库模块的常规queue.Empty和queue.Full异常queue被引发以指示超时。

Queue实现queue.Queue除 task_done()和的所有方法join()。

qsize()
返回队列的大致大小。由于多线程/多处理语义,这个数字不可靠。

请注意,这可能会NotImplementedError在sem_getvalue()未实现的Mac OS X等Unix平台上引发。

empty()
True如果队列为空False则返回,否则返回。由于多线程/多处理语义,这是不可靠的。

full()
True如果队列已满,False则返回,否则返回。由于多线程/多处理语义,这是不可靠的。

put(obj [,block [,timeout ] ] )
将obj放入队列中。如果可选参数块是True (缺省值)并且timeout是None(缺省值),则在必要时阻塞,直到空闲插槽可用。如果timeout是一个正数,则它会阻止最多超时秒,queue.Full如果在该时间内没有可用的空闲槽,则会引发异常。否则(块为 False),如果空闲插槽立即可用,则将项目放在队列中,否则引发queue.Full异常(在这种情况下忽略超时)。

put_nowait(obj )
相当于。put(obj, False)

get([ block [,timeout ] ] )
从队列中删除并返回一个项目。如果可选的args 块是 True(默认值),超时是None(默认值),则在必要时阻止,直到某个项可用。如果timeout是一个正数,它会阻止最多超时秒,queue.Empty 如果在该时间内没有可用的项,则会引发异常。否则(块为 False),如果一个项立即可用则返回一个项,否则引发 queue.Empty异常(在这种情况下忽略超时)。

get_nowait()
相当于get(False)。

multiprocessing.Queue还有一些其他方法没有找到 queue.Queue。大多数代码通常不需要这些方法:

close()
表示当前进程不再将此数据放入此队列。一旦将所有缓冲数据刷新到管道,后台线程将退出。当队列被垃圾收集时,会自动调用此方法。

join_thread()
加入后台主题。这只能在close()被调用后使用。它会阻塞,直到后台线程退出,确保缓冲区中的所有数据都已刷新到管道。

默认情况下,如果进程不是队列的创建者,则在退出时它将尝试加入队列的后台线程。这个过程可以调用 cancel_join_thread()make join_thread()什么都不做。

cancel_join_thread()
防止join_thread()阻塞。特别是,这可以防止后台线程在进程退出时自动连接 - 请参阅join_thread()。

这个方法的更好名称可能是 allow_exit_without_flush()。它可能会导致排队数据丢失,您几乎肯定不需要使用它。只有当你需要当前进程立即退出而不等待将排队数据刷新到底层管道时,才真正有效,而你并不关心丢失的数据。

注意 此类的功能需要在主机操作系统上运行共享信号量。没有一个,这个类中的功能将被禁用,并且尝试实例化一个Queue将导致一个ImportError。有关其他信息,请参阅 bpo-3770。对于下面列出的任何专用队列类型也是如此。
生产者消费者模型

假设有5000个网页,把网页中所有的url链接拿到
第一步: 获取网页
遇到的情况:
网页服务的响应速度特别慢
网速慢
结果:
获取结果需要的时间长,如2秒
第二步:从网页内容中提取链接
一般拿到结果后获取链接的时间特别短,因为计算机处理速度比较快,而且网页内容比较少。如0.02秒
整个结果需要2.02秒
那5000个网页需要50002.02秒。
如果用并发有5000个任务,起50个进程,需要100次, 时间是100
2.02秒
由于从网络上获取结果时间长,提取链接时间短,起50个进程做第一件事:获取网页。2秒能拿到50个结果,一共再开一个进程专门算,一边拿到50个结果后,需要50*0.02秒=1秒,接下来又拿到50个结果,再继续算,以此类推,两者是并行的。
当需要完成一个操作,但是此操作要分两步,这两步的时间很不协调,如果一一去操作,结果会很慢,于是把一个任务分成两部分,第一部分叫生产数据,第二部分叫消费数据,也叫处理数据。然后在中间放一个盘子,左边生成完数据放到盘子里, 右边处理就可以了。如果盘子空了就证明生产数据慢,处理数据快了。此时添加生产数据的进程就可以了。反之,增加处理数据的进程就可以了。使他们达到一个平衡的状态。最终达到协调和最快的效果。
在这里插入图片描述

import time
import random
from multiprocessing import Process,Queue
def consumer(q, name):  # 消费者  所有的处理数据的内容,也就是提取链接
    while True:
        food = q.get()
        if food is None:break  # 都到空程序就结束
        print('%s吃了一个%s' % (name, food))
        time.sleep(random.uniform(0.5, 1))  # 吃也需要时间

def producer(q, name, food):  # q:生产的数据  name:谁生产的这个数据  food:什么数据
    for i in range(10):  # 每生产十个数据
        time.sleep(random.uniform(0.3, 0.8))  # 模拟访问网页的时间   0.3到0.8之间的小数
        print('%s生产了%s%s' % (name, food, i))
        q.put(food + str(i))

if __name__ == '__main__':
    q = Queue()
    c1 = Process(target=consumer, args=(q, 'laura'))
    c2 = Process(target=consumer, args=(q, 'danny'))
    c1.start()
    c2.start
    p1 = Process(target=producer, args=(q, 'wendy', 'baozi'))
    p2 = Process(target=producer, args=(q, 'iris', 'beizi'))
    p1.start()
    p2.start()
    p1.join() # 代表生产完了。
    p2.join()
    q.put(None)   # 当所有生产者生产完所有数据之后,再put None
    q.put(None)  # 有几个consumer就需要放几个None

运行结果

iris生产了beizi0
laura吃了一个beizi0
wendy生产了baozi0
wendy生产了baozi1
iris生产了beizi1
laura吃了一个baozi0
wendy生产了baozi2
iris生产了beizi2
laura吃了一个baozi1
iris生产了beizi3
wendy生产了baozi3
iris生产了beizi4
laura吃了一个beizi1
iris生产了beizi5
wendy生产了baozi4
laura吃了一个baozi2
iris生产了beizi6
wendy生产了baozi5
iris生产了beizi7
wendy生产了baozi6
iris生产了beizi8
laura吃了一个beizi2
wendy生产了baozi7
iris生产了beizi9
wendy生产了baozi8
laura吃了一个beizi3
wendy生产了baozi9
laura吃了一个baozi3
laura吃了一个beizi4
laura吃了一个beizi5
laura吃了一个baozi4
laura吃了一个beizi6
laura吃了一个baozi5
laura吃了一个beizi7
laura吃了一个baozi6
laura吃了一个beizi8
laura吃了一个baozi7
laura吃了一个beizi9
laura吃了一个baozi8
laura吃了一个baozi9
joinablequeue

Joinablequeue是一个类,继承multiprocessing.Queue,所以它也是一个队列,除了可以使用父类中所有方法外,附加了task_done() 和 join() 方法,

class JoinableQueue(multiprocessing.Queue):
    def task_done(self):   # 通知队列已经有一个数据被处理了
        pass

    def join(self):   # 阻塞直到放入队列中所有的数据都被处理掉(有多少个数据就接收到了多少个taskdone)
        pass

在这里插入图片描述
还是刚刚的例子,使用JoinableQueue就不用手动put None了,只需task_done和join配合就行了

import time
import random
from multiprocessing import Process,JoinableQueue
def consumer(q, name):
    while True:
        food = q.get()
        time.sleep(random.uniform(0.5, 1))
        print('%s吃了一个%s' % (name, food))
        q.task_done()

def producer(q, name, food):
    for i in range(10):
        time.sleep(random.uniform(0.3, 0.8))
        print('%s生产了%s%s' % (name, food, i))
        q.put(food + str(i))

if __name__ == '__main__':
    jq = JoinableQueue()
    c1 = Process(target=consumer, args=(jq, 'laura'))
    c2 = Process(target=consumer, args=(jq, 'wendy'))
    c1.daemon = True   # 随着主进程的代码结束而结束
    c2.daemon = True
    c1.start()
    c2.start()
    p1 = Process(target=producer, args=(jq, 'sandy', 'shui'))
    p2 = Process(target=producer, args=(jq, 'iris', 'cha'))
    p1.start()
    p2.start()
    p1.join()  # 生产者要先把所有的数据都放到队列中。这样jq里面才有20个数据。下面的阻塞收到的task_done才有效。如果没有p.join可能还没来的及放完就等待了,jq.join就结束了,c是守护进程也结束了。整个程序执行到一半就结束了。如果没生产完就阻塞
    p2.join()
    jq.join()  # 队列中的任务全部被处理完了

猜你喜欢

转载自blog.csdn.net/weixin_42233629/article/details/82974531