【手撕代码】Python协程实现生产者消费者模型
2020-07-28 本文已影响0人
时间煮菜
什么是协程?
- 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
- 协程的实现为协作式而非抢占式的,这是和进程线程的最大区别。
- 协程拥有自己的寄存器上下文和栈。
- 协程是单线程工作,没有多线程需要考虑的同时写变量冲突,所以不需要多线程的锁机制,故执行效率比多线程更高。
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
什么是生成器?
生成器定义
- 生成器(generator):Python中一遍循环一遍计算的机制,称为生成器。
为什么需要生成器
-
列表中所有的数据都存在内存中,如果是海量数据的话,会非常耗费内存
-
如:我们只需要获取列表中的前两个数据的话,所有的数据都需要读到内存中,这样其他的空间都会浪费掉
-
这个时候生成器的作用就凸显出来了,生成器可以将列表中的元素按照某种算法推算出来,我们可以在循环的过程中不断推算出后序的元素,这样就不必将一整个list都存在内存中,能够大量节省空间
-
总的来说,如果我们使用海量数据,又想让它占用的空间小,那么就使用生成器
创建生成器的两种方法
- 将列表生成器的
[]
改为()**
,这样就得到了一个生成器
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(生成器)
- 只要函数中使用到了
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跳转到消费者接受执行,等待消费者消耗完毕后,生产者继续生成,提高了效率。