协程 - downtiser/python-one GitHub Wiki

协程,又叫微线程,即用户态的轻量级线程,能够在单线程下实现多并发

协程特点:

  1. 必须是在单线程里实现的多并发
  2. 修改共享数据不需要加锁(因为实际上只有一个线程)
  3. 用户程序里保存多个数据流的上下文和栈
  4. 遇到I/O操作会自动切换到其他协程

协程优点:

  1. 无需线程上下文切换的开销
  2. 无需原子操作锁定(其实就是修改共享变量数据加锁) 及同步的开销
  3. 方便切换控制流(后面用greenlet模块和gevent模块可以方便的在几个协程间切换),简化代码
  4. 执行I/O密集型任务时效率特别高,成本低,对于CPU来说,处理协程很轻松,一个CPU可以处理上万个协程

在学习生成器时用yield写的生产者消费者模型就是一个简单的协程,不过比较麻烦,而且实现不了第四个特点,于是要引入另外两个模块greenletgevent

greenlet其实就是yield升级版,用它的内置方法切换协程比较方便

#Downtiser
import greenlet

def fun1():
    print('green1')
    g2.switch()  #切换到第二个协程
    print('green3')
    g2.switch()

def fun2():
    print('green2')
    g1.switch()
    print('green4')

g1 = greenlet.greenlet(fun1) #启动一个协程
g2 = greenlet.greenlet(fun2)
g1.switch() #切换到第一个协程

结果>>>

green1
green2
green3
green4

greenlet还是实现不了I/O操作切换,得靠gevent第三方包才行,它其实是对greenlet的更上层封装,是一个C扩展模块

下面这个脚本使用gevent起了三个协程,每个协程模拟了一下I/O操作,耗时都不同

#Downtiser
import gevent, time


def fun1():
    print('fun1 开始执行I/O操作......')
    gevent.sleep(3)  #模拟I/O过程
    print('fun1 执行完毕!')

def fun2():
    print('fun2 开始执行I/O操作......')
    gevent.sleep(2)
    print('fun2 执行完毕!')

def fun3():
    print('fun3 开始执行I/O操作......')
    gevent.sleep(0)  #只是为了执行一次切换操作
    print('fun3 执行完毕!')

if __name__ == '__main__':
    start_time = time.time()
    gevent.joinall([gevent.spawn(fun1), gevent.spawn(fun2), gevent.spawn(fun3)])  #创建协程
    print('cost>>>%s sec'%(time.time()-start_time))

结果>>>

fun1 开始执行I/O操作......
fun2 开始执行I/O操作......
fun3 开始执行I/O操作......
fun3 执行完毕!
fun2 执行完毕!
fun1 执行完毕!
cost>>>3.0331170558929443 sec
  • gevent起的协程遇到I/O操作,就会立即切换至其他协程,所以一走到协程1的gevent.sleep(3)就切换到协程2,协程2也要执行I/O操作,于是切换到协程3,协程3也要,于是又切换回协程1,但协程1还没执行完,所以又切换到协程2....... 最后得到结果,本来要执行5秒的脚本只执行了3秒,协程的优点4可见一斑.