python消费者生产者模式和协程

301 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天

生产者与消费者模式

生产者和消费者:两个线程之间的通讯

Python的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifeQueue,和优先队列PiorityQueueu。这些队列都实现了锁的原理(可以理解为原子操作,即要么不做要么做完),能够在多线程中直接使用。可以使用队列来实现线程间同步问题

  • 例子
import threading
import queue
import random
import time

def produce(q):
    i = 0
    while i < 10:
        num = random.randint(1,100)
        q.put('生产者产生数据:%d'%num)
        print('生产者产生数据:%d'%num)
        time.sleep(1)
        i += 1
	q.put(None)
    #完成任务
    q.task_done()
def consume(q):
    while Ture:
        item = q.get()
        if item is None:
            break
		print('消费者获取的数据:%s'%item)
        time.sleep(4)
	#完成任务
    q.task_done()

if __name__ == '__main__':
    q = queue.Queue(10)
    arr = []
    
    #创建生产者
    th = threading.Thread(target=produce,args=(q,))
    th.start()
    
    #创建消费者
    tc = threading.Thread(target=consume,args=(q,))
    tc.start
    
    th.join()
    tc.join()
    print('END')

协程(微线程)

定义

一般用于耗时操作

耗时操作举例:网络请求,网络下载(爬虫),IO(文件读写),阻塞等

协程不同于进程和线程,在原有的python中它是依靠生成器来完成的

  • 示例
import time
def task1():
    for i in range(3):
        print('A'+str(i))
        yield
        time.sleep(1)
def task2():
    for i in range(3):
        print('B'+str(i))
        yield
        time.sleep(2)    
if __name__ == '__main__':
    g1 = task1()
    g2 = task2()
    
    while Ture:
        try:
            next(g1)
            next(g2)
		except:
            break

由于每次使用协程都得自己定义生成器,所以这里有一个协程的第三方库

greenlent

安装库

pip install greenlent
  • 库的使用示例
import time
from greenlent import greenlent
def a(): 
    for i in range(5):
        print('A'+str(i))
        gb.switch()
        time.sleep(0.1)
def b(): 
    for i in range(5):
        print('B'+str(i))
        gc.swtich()
        time.sleep(0.1)
def c(): 
    for i in range(5):
        print('C'+str(i))
        ga.switch()
        time.sleep(0.1)     
        
if __name__ == '__main__':
    ga = greenlet(a)
    gb = greenlet(b)
    gc = greenlet(c)
    
    ga.switch()

拿到这里,我们发现这种方法和用生成器去写似乎并没有什么太大的优化,那么下面就是更智能的方法。

gevent和猴子补丁

介绍

greenlet已经实现了协程,但是这个人工切换还是太麻烦了,为了解脱双手,我们引入了更强大的并且能自动切换任务的模块gevent其原理是当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO完成,再适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待中,有了gevent我们自动切换协程,就保证了总有greenlet运行,而不是等待IO

安装

pip install gevent
  • 示例
import time
from greenlent import greenlent
def a(): 
    for i in range(5):
        print('A'+str(i))
        time.sleep(0.1)
def b(): 
    for i in range(5):
        print('B'+str(i))
        time.sleep(0.1)
def c(): 
    for i in range(5):
        print('C'+str(i))
        time.sleep(0.1)   
  
if __name__ == '__main__':
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)
    
    g1.join()
    g2.join()
    g3.join()
  • 注意,上述的代码运行结果并不会达到我们想要的交替运行结果我们想要达到协程的效果就必须得把猴子召唤过来然后让猴子给我们的程序打上补丁
from gevent import monkey
monkey.patch_all()
  • 上述程序只有在加入这两行后才能达到我们协程想要的效果

问题:为什么要把猴子叫过来打补丁

  • 因为如果要用原来的time内的sleep,gevent内部是感知不到阻塞的,所以用完猴子补丁后,协程的阻塞就会被gevent的内部捕捉并且智能切换了,而你如果用gevent内置的sleep就不用把猴子召唤过来打补丁了(说白了就是猴子打的补丁就是sleep那块缺失的自动检测阻塞功能)