Python-协程
协程(coroutine),又称微线程,纤程,是一种用户级别的轻量级线程,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存寄存器上下文和栈。因此协程能保留上一次调用时的状态,每次过程重入时,就相当于上一次调用的状态。在并发编程中,协程与县城类似,每个协程表示一个执行单元,有自己本地的数据库,与其他协程共享全局数据和其他资源。
优点:
1.无需线程上下文切换的开销
2.无需原子操作锁定及同步的开销
3.方便切换控制流,简化编程模型
4.高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
1.无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
2.进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。
听过第三方库gevent对协程的支持,本质上是greenlet在实现切换工作。
greenlet工作流程:
访问网络的IO操作时,出现阻塞,greenlet就显式的切换到另一端没有被阻塞的代码段执行,直到先前的阻塞状况小时候,再自动切换回原来的代码段进行处理。
由于IO操作的耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO,这就是携程比多线程高效的原因。由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的标准库,将一些常见的阻塞。如socket、select等地方实现协程跳转,这一过程在启动时通过monkey path完成。
gevent:
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Fade Zhao' from gevent import monkey; monkey.patch_all() import gevent import urllib.request def run_task(url): print('Visit --> %s' % url) try: response = urllib.request.urlopen(url) data = response.read() print('%d bytes received from %s.' % (len(data), url)) except Exception as e: print(e) if __name__=='__main__': urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/'] greenlets = [gevent.spawn(run_task, url) for url in urls ] gevent.joinall(greenlets) ''' Visit --> https://github.com/ Visit --> https://www.python.org/ Visit --> http://www.cnblogs.com/ 46181 bytes received from http://www.cnblogs.com/. 51474 bytes received from https://github.com/. 48879 bytes received from https://www.python.org/. ''' 以上程序使用了spawn和joinall函数 spawn: 用来创建协程 joinall: 用来批量添加并运行协程 从结果来看,三个协程是并发的,而且结束顺序不同,但其实只有一个线程。
gevent也有对池的支持。当拥有动态数量的greenlet需要进行并发管理(限制并发数)时,就可以使用池,
这在处理大量网络IO是非常重要的。
from gevent import monkey monkey.patch_all() import urllib.request from gevent.pool import Pool def run_task(url): print('Visit --> %s' % url) try: response = urllib.request.urlopen(url) data = response.read() print('%d bytes received from %s.' % (len(data), url)) except Exception as e: print(e) return 'url:%s --->finish'% url if __name__=='__main__': pool = Pool(2) urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/'] results = pool.map(run_task,urls) print(results) ''' >>> Visit --> https://github.com/ Visit --> https://www.python.org/ 48879 bytes received from https://www.python.org/. Visit --> http://www.cnblogs.com/ 46184 bytes received from http://www.cnblogs.com/. 51474 bytes received from https://github.com/. ['url:https://github.com/ --->finish', 'url:https://www.python.org/ --->finish', 'url:http://www.cnblogs.com/ --->finish'] ''' 可以看出,Pool对象确实对协程的并发数量进行了管理,先访问了前两个网址,当其中一个任务完成时,才会执行第三个