06 进程间通信IPC机制 队列

 一、python进程间通信   

IPC机制(Inter-Process Communication)

总结:
IPC机制 进程间通信 进程间数据是隔离的,一般情况下是无法进行数据交互的 但是可以通过一些手段实现通信 1.管道:(例如subprocess中的PIPE,只能用read取一次值再去取就报错了)效率太低,不适合频繁交互 2.队列(管道
+锁实现):更强大,我们着重研究这个就好了
数据只有一份,取完就没了 无法重复获取用一份数据

 

 

1.1 队列  Queue模块

这个是进程模块中的队列Queue,是进程模块封装出来的高级队列,进程中还有可等待队列JoinableQueue

线程模块中没有队列,python有原生的队列模块(见下)

python原生的queue模块中包含:

queue.Queue(),先进先出

queue.LifoQueue(),先进后出

queue.PriorityQueue(),自定义优先级队列

 

        from multiprocessing import Queue
        q = Queue()  # 括号内支持传数字 限制的是队列的大小
        # 队列: 先进先出
        # 堆栈: 先进后出
q.put()放队列中放数据 q.get()从队列中取数据 # 上面两个方法在存放值和取值的时候都会出现阻塞的情况(队列满了,队列空了) q.full()判断队列是否存满(默认为几十亿) q.empty()判断队列是否为空 q.get_nowait()取值一旦没有值不等待直接报错 # 上面三个方法在多进程/多线程不适用

 

from multiprocessing import Queue

q = Queue(3)  # 括号内可以传参数 表示的是这个队列的最大存储数,不写基本上是无穷
# 往队列中添加数据
q.put(1)
q.put(2)
# print(q.full())  #False 判断队列是否满了
q.put(3)
# print(q.full())  #True
# q.put(4)  # 当队列满了之后 再放入数据 不会报错 会原地等待 直到队列中有数据被取走(阻塞态)

print(q.get())
print(q.get())
print(q.empty())  #False 判断队列中的数据是否取完
print(q.get())
print(q.empty()) # True
# print(q.get_nowait())  # 取值 没有值不等待直接报错
# print(q.get())  # 当队列中的数据被取完之后 再次获取 程序会阻塞 直到有人往队列中放入值
"""
full
get_nowait
empty
都不适用于多进程的情况
"""

1.2 通过队列实现进程间通讯

Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递

验证通讯思路:
      子进程放数据 主进程获取数据
      两个子进程相互放、取数据

def producer(q):
    q.put('hello GF~')

def consumer(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer,args=(q,))
    c = Process(target=consumer, args=(q,))
    p.start()
    c.start()

#hello GF~  通讯成功

二、生产消费者模型 队列/可等待队列

生产者:生产/制造数据的
消费者:消费/处理数据的
例子:做包子的,买包子的两个问题
1.做包子远比买包子的多
2.没人买我不想做了,做包子的远比买包子的少
这就出现供需不平衡的问题
两者之间的通信介质:队列/管道

1.基于Queue

常规情况下包子买完后程序还停在那里不结束,在Queue队列下,在生产完之后有几个消费者就放入几个None,当两个人都取到None就停止,也行,但是太low,
from  multiprocessing import Process,Queue  #多进程组件,队列
import time,random  

#生产者方法
def producer(name,food,q):
    for i in  range(4):
        time.sleep(random.randint(1,3)) #模拟获取数据时间
        f = '%s生产的%s%s'%(name,food,i)
        print(f)
        q.put(f) #添加进队列

#消费者方法
def consumer(q,name):
    while True:
        food = q.get() #如果获取不到,会一直阻塞进程不会结束子进程
        # 当队列中的数据是None的时候结束while循环
        if food is None:
            print('%s获取到一个空'%name)
            break
        f = '%s消费了%s' % (name, food)
        print(f)
        time.sleep(random.randint(1,3)) # 模拟消耗数据时间

if __name__ == '__main__':
    q = Queue()  # 创建队列
    
    # 模拟生产者 生产数据
    p = Process(target=producer, args=('p', '包子', q)) #创建进程
    p.start() #启动进程
    p1 = Process(target=producer, args=('p1', '烧饼', q))
    p1.start()

   #模拟消费者消费数据
    c = Process(target=consumer, args=(q, 'c'))
    c.start()
    c1 = Process(target=consumer, args=(q, 'c1'))
    c1.start()

    
    p.join()#阻塞主进程 直到p和p1 子进程结束后才执行q.put() 方法
    p1.join()#阻塞主进程 直到p和p1 子进程结束后才执行q.put() 方法
  
    #为了确保生产者生产完所有数据后,
    #最后一个是None,方便结束子进程中的while循环,
    #否则会一直等待队列中加入新数据。
    q.put(None)
    q.put(None)
基于queue的生产消费者模型

使用Queue组件实现的缺点就是,实现了多少个消费者进程,就需要在最后往队列中添加多少个None,否则,p.get() 不到任务会阻塞子进程,因为while循环,直到队列q中有新的任务加进来,才会再次执行。而我们的生产者只能生产这么多东西,所以相当于程序卡死

2.基于JoinableQueue

进程模块中有,原生queue模块中没有

我们使用高级一点的能够被等待的队列JoinableQueue模块

复制代码
JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:

q.task_done() 
使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。

q.join() 
生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。 
下面的例子说明如何建立永远运行的进程,使用和处理队列上的项目。生产者将项目放入队列,并等待它们被处理。
复制代码

 

复制代码
from multiprocessing import Process,Queue,JoinableQueue
import random
import time


def producer(name,food,q):
    for i in range(10):
        data = '%s生产了%s%s'%(name,food,i)
        time.sleep(random.random())
        q.put(data)
        print(data)

def consumer(name,q):
    while True:
        data = q.get()
        if data == None:break
        print('%s吃了%s'%(name,data))
        time.sleep(random.random())
        q.task_done()  # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了



if __name__ == '__main__':
    q = JoinableQueue()

    p = Process(target=producer,args=('大厨egon','馒头',q))
    p1 = Process(target=producer,args=('跟班tank','生蚝',q))
    p.start()
    p1.start()

    c = Process(target=consumer,args=('兆龙',q))
    c1 = Process(target=consumer,args=('吃货jerry',q))
c.daemon
= True #将两个消费者作为守护进程,配合尾部q.join,只要取完队列主立马死,两个消费者也跟着死 c1.daemon = True
c.start() c1.start()
p.join() p1.join() q.join()
# 等到队列中数据全部取出再结束主进程(和Queue中的q.empty类似但是empty只能用于单进程)

# q.put(None) # q.put(None) #在生产完之后放入两个None,当两个人都取到None就停止,也行,但是太low
复制代码

三、python原生queue(堆栈、自定义优先级队列)

【问题】同一个进程下的多个线程本来就是数据共享,数据上锁就好了,为什么还要用队列?

  •   因为锁操作的不好极容易产生死锁现象
  •   队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 
1.正常队列 先进先出
q = queue.Queue()
q.put('hahha')
print(q.get())
print(q.get(block=False)) # 取不到不阻塞直接报错
2.先进后出 栈
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  #3

3.自定义优先级 越小越优先
q = queue.PriorityQueue()
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())  #(-10, 'yyyy')

 四、双端队列

deque实现了列表的双向插入和删除操作提高效率,适合用于队列和栈:

  •  append
  •  appendleft
  •  pop
  •  popleft
from collections import deque
q = deque(['a','b','c'])
q.append(1)
q.appendleft(2)
print(q)  #deque([2, 'a', 'b', 'c', 1])
print(q.pop())  # 1
print(q.popleft())  # 2

#了解一下
#队列不应该支持任意位置插值,只能在首尾插值(不能插队是吧),但是双向队列有个傻子方法,插入,乱插
 from collections import deque
q = deque(['a','b','c'])
q.insert(1,'哈哈哈')  # 特殊点:双端队列可以根据索引在任意位置插值
print(q)  #deque(['a', '哈哈哈', 'b', 'c'])  

 


  

 

 

posted @ 2020-04-10 11:37  www.pu  Views(195)  Comments(0Edit  收藏  举报