目录
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
协程又被称为微线程,协程的完成主要靠yield关键字,协程执行过程中,在子程序内部可中断,将当前运行的上下文挂起,然后转而执行别的子程序,在适当的时候再返回来接着执行。
python中实现并发的方式有很多种,通过多进程并发可以真正利用多核资源,而多线程并发则实现了进程内资源的共享,然而Python中由于GIL锁机制,多线程实际上是伪多线程;
对于计算密集型程序,应该使用多进程并发充分利用多核资源,而在IO密集型程序中,多核优势并不明显,甚至由于大多数时间都是在IO堵塞状态,多进程的切换消耗反而让程序效率更加低下;
而当需要并发处理IO密集型任务时,就需要用到协程(Coroutine)。协程并没有系统级的调度,而是用户级的调度方式,避免了系统调用的开销,虽然协程最终是串行工作,但是却可以实现非常大的并发量。通过多进程+协程的方式,可以有效均衡多核计算和请求等待。
1.带有yield关键字的函数自动变成生成器
2.生成器被调用时不会立即执行
3.对于生成器,当调用函数next(generator)时,将获得生成器yield后面表达式的值;
4.当生成器已经执行完毕时,再次调用next函数,生成器会抛出StopIteration异常
定义一个使用yield的foo()方法
- def foo():
- while True:
- print("======这是yield之前前前前====")
- x = yield
- print("x当前值:{0}".format(x))
- print("======这是yield之后后后后====")
使用next()和send()
- if __name__ == '__main__':
- g = foo()
- # 第一次调用next的时候,程序从函数最开始处运行,执行到yield处,停在该处
- next(g)
- print("\n=====================下 一 个====================\n")
- next(g)
- print("\n=====================下 一 个====================\n")
- # send将参数赋给yield的返回值,然后该返回值赋给了变量x
- # 继续程序的执行,直到下一次遇到yield停下来
- g.send(1)
- # next就相当于send(None)
- print("\n=====================下 一 个====================\n")
- g.send(None)

我们可以发现,实际上就是在yield关键字的位置停止程序向下执行,同时记住当前执行到的位置,下次继续在这个位置向后执行,直到结束或者又遇到yield。
我们发现,整个程序过程中没有用到锁,因为协程只有一个线程,也不存在同时写变量冲突,所以在协程中控制共享资源不需要加锁,只需要判断状态即可。
- def cousume():
- r = ''
- while True:
- n = yield r
- if not n:
- return
- print("消费者说:消费者消费了第{0}个".format(n))
- time.sleep(1)
- r = '用了'
-
- def produce(c):
- next(c)
- n = 0
- while n < 3:
- n += 1
- print("生产者说:生产者生产了第{0}个:".format(n))
- r = c.send(n)
- print("生产者说:消费者反馈-- {0}".format(r))
- c.close()
- if __name__ == '__main__':
- c = cousume()
- produce(c)
- print("结束")

注意
produce和consume函数是在一个线程内执行,通过调用send方法和yield互相切换
子程序就是协程的一种特例。 —— 唐纳德·克努特
欢迎点赞、收藏、评论区交流,转载标明出处。