欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

生产者-消费者模型的 Python 并发实现

最编程 2024-06-09 08:37:58
...

什么是协程?

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

什么是生成器?

生成器定义
  • 生成器(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跳转到消费者接受执行,等待消费者消耗完毕后,生产者继续生成,提高了效率。

推荐阅读