美文网首页
【手撕代码】Python协程实现生产者消费者模型

【手撕代码】Python协程实现生产者消费者模型

作者: 时间煮菜 | 来源:发表于2020-07-28 10:55 被阅读0次

    什么是协程?

    • 协程是一种用户态的轻量级线程,协程的调度完全由用户控制
    • 协程的实现为协作式而非抢占式的,这是和进程线程的最大区别。
    • 协程拥有自己的寄存器上下文和栈
    • 协程是单线程工作,没有多线程需要考虑的同时写变量冲突,所以不需要多线程的锁机制,故执行效率比多线程更高
    • 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

    什么是生成器?

    生成器定义
    • 生成器(generator):Python中一遍循环一遍计算的机制,称为生成器。
    为什么需要生成器
    • 列表中所有的数据都存在内存中,如果是海量数据的话,会非常耗费内存

    • 如:我们只需要获取列表中的前两个数据的话,所有的数据都需要读到内存中,这样其他的空间都会浪费掉

    • 这个时候生成器的作用就凸显出来了,生成器可以将列表中的元素按照某种算法推算出来,我们可以在循环的过程中不断推算出后序的元素,这样就不必将一整个list都存在内存中,能够大量节省空间

    • 总的来说,如果我们使用海量数据,又想让它占用的空间小,那么就使用生成器

    创建生成器的两种方法
    1. 将列表生成器的[]改为()**,这样就得到了一个生成器
    In [11]: l = [x * x for x in range(5)]
    
    In [12]: l
    Out[12]: [0, 1, 4, 9, 16]
    
    In [13]: g = (x * x for x in range(5))
    
    In [14]: g
    Out[14]: <generator object <genexpr> at 0x03C76530> 
    

    如打印所示,g使用()创建了generator(生成器)

    1. 只要函数中使用到了yield关键字,那么这个函数就是一个生成器,调用这个函数就会创建一个(generator)对象。
      • 在下面的生产者消费者模型中,我们就使用到了yield协程实现
      • yield相当于return一个值,并且记住这个返回的位置,下一次迭代时,代码从yield的下一条语句开始执行。
      • 可以使用next()来调用生成器对象取值。(用for循环实现迭代)
      • send()和next()一样,都能让生成器继续往下走(遇到yield停),区别在于send()能传一个值,这个值作为yield表达式整体的效果。(也就是send可以强行修改上一个yield表达式值,看下面的栗子感受一下 )
      • next()方法相当于send(None)

    第一次next()调用,遇到yield就停,yield返回了i值为0,a没有赋到值;

    第二次next()调用,接着yield下面的语句走,打印a,由于a没有赋到值,返回None,i+1, 遇到yield停返回i值为1,a还是没有赋到值

    第三次send()调用,send传值“Hello World”,这个send值作为yield表达值整体的效果,也就是强行修改yield i 为 “Hello World” 并且传递给a,a有值了,下面打印a就为 “Hello World” ,i+1,遇到yield停返回i值为2。

    In [30]: def test():
        ...:     i = 0
        ...:     while i < 5:
        ...:         a = yield i  # 遇到yield就停
        ...:         print(a)
        ...:         i += 1
        ...:
    In [31]: b = test()
    
    In [32]: next(b)
    Out[32]: 0
    
    In [33]: next(b)
    None
    Out[33]: 1
    
    In [34]: b.send(5)
    5
    Out[34]: 2
    
    In [35]: next(b)
    None
    Out[35]: 3
    

    关键:下一次迭代,代码从yield的下一条语句开始执行。

    下面讲解重点yield实现协程(生产者-消费者)

    yield实现生产者-消费者模型

    # 消费者
    def customer():
        r = ""
        while True:
            n = yield r  # 接受生产者的消息n,并且发送r
            print("customer 接受:", n)
            r = "ok"
    
    
    # 生产者
    def producer(c):
        c.send(None)  # 第一次返回None,不然会报错
    
        for i in range(6):
            print("开始发送给消费者:", i)
            r = c.send(i)  # 向消费者发送值
            print("接受到消费者:", r)
            print("------------------------")
    
    c=customer()
    producer(c)
        
    

    打印效果:

    开始发送给消费者: 0
    customer 接受: 0
    接受到消费者: ok
    ------------------------
    开始发送给消费者: 1
    customer 接受: 1
    接受到消费者: ok
    ------------------------
    开始发送给消费者: 2
    customer 接受: 2
    接受到消费者: ok
    ------------------------
    开始发送给消费者: 3
    customer 接受: 3
    接受到消费者: ok
    ------------------------
    开始发送给消费者: 4
    customer 接受: 4
    接受到消费者: ok
    ------------------------
    开始发送给消费者: 5
    customer 接受: 5
    接受到消费者: ok
    ------------------------
    
    
    图解过程
    • 这里的 n = yield r , 不只是发送数据r,还进行接受n
    • 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
    • 如果该用yield实现协程,生产者生产消息后,直接通过yield跳转到消费者接受执行,等待消费者消耗完毕后,生产者继续生成,提高了效率。

    相关文章

      网友评论

          本文标题:【手撕代码】Python协程实现生产者消费者模型

          本文链接:https://www.haomeiwen.com/subject/lexfrktx.html