协程(Coroutine),又称微线程,纤程。协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其它地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上次调用的状态,换种说法:进入上次离开时所处逻辑流的位置。
协程的优点:
无需线程上下文切换的开销(因为是单线程)
无需原子操作(不会被线程调度机制打断的操作,如修改一个变量)锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题,所以很适用于高并发处理。
缺点:
无法利用多核资源:协程的本质是单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是CPU密集型应用。
进行阻塞操作会阻塞掉整个程序。
消费者模型模拟协程
# coding=utf-8 def producer(): c1.next() # py3用__next__方法 c2.next() n = 1 while n < 6: print 'producer is making food {}'.format(n) c1.send(n) # send表示唤醒生成器的同时发送一个值 c2.send(n) n += 1 def consumer(name): print 'Start eating' while True: food = yield # 遇到yield的时候停止,被唤醒的时候传值给food print '{} eating {}'.format(name, food) c1 = consumer('c1') c2 = consumer('c2') p = producer() 结果: Start eating Start eating producer is making food 1 c1 eating 1 c2 eating 1 producer is making food 2 c1 eating 2 c2 eating 2 producer is making food 3 c1 eating 3 c2 eating 3 producer is making food 4 c1 eating 4 c2 eating 4 producer is making food 5 c1 eating 5 c2 eating 5
Nginx就是利用的协程实现的高并发,它默认是单线程,遇到IO操作时就切走执行其它CPU操作,IO操作完毕时再切回。
greenlet实现简单协程中的切换
# coding=utf-8 from greenlet import greenlet def f1(): print 12 gb.switch() print 56 def f2(): print 34 ga.switch() print 78 ga = greenlet(f1) gb = greenlet(f2) ga.switch() 结果: 12 34 56
模拟协程中的切换
# coding=utf-8 import gevent def f1(): print 12 gevent.sleep(2) print 1112 def f2(): print 34 gevent.sleep(1) print 78 def f3(): print 56 gevent.sleep(1) print 910 gevent.joinall([ gevent.spawn(f1), gevent.spawn(f2), gevent.spawn(f3), ]) 输出: 12 34 56 78 910 1112
模拟实际应用
在本地用一个Django的视图函数模拟实际IO处理 urls.py from django.conf.urls import include, url from django.contrib import admin from main.views import * urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^mysleep/(?P<second>d+)/', mysleep, name='mysleep'), ] views.py from django.http import JsonResponse import time # Create your views here. def mysleep(request, second): time.sleep(int(second)) return JsonResponse({'status': 'ok'})
# 常规的串行效果 # coding=utf-8 import gevent import requests import time def f(second): r = requests.get('http://127.0.0.1:8000/mysleep/{}/'.format(second)) if __name__ == "__main__": time_start = time.time() f(1) f(2) print u'串行总计:{}秒'.format(time.time() - time_start) 输出: 串行总计:3.09899997711秒 # 用协程实现并行 # coding=utf-8 import gevent import requests import time from gevent import monkey monkey.patch_all() # 把当前程序的IO操作做标记 def f(second): r = requests.get('http://127.0.0.1:8000/mysleep/{}/'.format(second)) if __name__ == "__main__": time_start = time.time() gevent.joinall([ gevent.spawn(f, 1), gevent.spawn(f, 2) ]) print u'并行总计:{}秒'.format(time.time() - time_start) 输出: 并行总计:2.03000020981秒