什么是协程
a coroutine is a function that can suspend its execution (yield) until the given yieldinstruction finishes.
也就是说,协程是一个函数,可以被挂起和被恢复。
协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程那样需要上下文切换来消耗资源,因此协程的开销远远小于线程的开销。
子程序
子程序,就是函数,在所有语言中都是层级调用,比如a调用b,b在执行过程中又调用了c,c执行完毕返回,b执行完毕返回,最后是a执行完毕。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似cpu的中断。比如子程序a、b:
def a(): print '1' print '2' print '3' def b(): print 'x' print 'y' print 'z'
假设由协程执行,在执行a的过程中,可以随时中断,去执行b,b也可能在执行过程中中断再去执行a,结果可能是:
1 2 x y 3 z
但是在a中是没有调用b的,所以协程的调用比函数调用理解起来要难一些。
看起来a、b的执行有点像多线程,但协程的特点在于是一个线程执行,那
和多线程比,协程有何优势?
1. 切换开销小,执行效率高
因为子程序切换不是线程切换,而是由程序自身控制
2. 不需要多线程的锁机制
因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了
子程序与协程的差异:
协程可以通过yield(取其“让步”之义而非“出产”)来调用其它协程,接下来的每次协程被调用时,从协程上次yield返回的位置接着执行,通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的
1. 子程序可以调用其他子程序,调用者等待被调用者结束后继续执行,故而子程序的生命期遵循后进先出,即最后一个被调用的子例程最先结束返回。协程的生命期完全由对它们的使用需要来决定。(也就是想怎么跳就怎么跳,不需要遵循函数调用那套规则)
2. 子程序的起始处是惟一的入口点,而协程可以有多个入口点,协程的起始处是第一个入口点,每个yield返回出口点都是再次被调用执行时的入口点。
3. 子例程只在结束时一次性的返回全部结果值。协程可以在yield时不调用其他协程,而是每次返回一部分的结果值,这种协程常称为生成器或迭代器。
子例程是协程的特里,因为任何子例程都可以看作是不调用yield的协程
示例
这里是一个简单的例子证明协程的实用性。
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
var q := 新建队列 coroutine 生产者 loop while q 不满载 建立某些新产品 向 q 增加这些产品 yield 给消费者 coroutine 消费者 loop while q 不空载 从 q 移除某些产品 使用这些产品 yield 给生产者
应用场景
根据今天查阅的资料来看,协程的应用场景主要在于 :i/o 密集型任务。
当程序在执行 i/o 时操作时,时间片的算法并不知道,就分配了时间片给他,此时cpu 是空闲的,因此可以充分利用 cpu 的时间片来处理其他任务。 - 在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 none )。(相当于被阻塞了,cpu就被浪费了) - 有了协程,我们在函数的执行过程中,如果遇到了耗时的 i/o 操作,函数可以临时让出控制权,让 cpu 执行其他函数,等 i/o 操作执行完毕以后再收回控制权。(相当于暂时跳过去)