协程,又称微线程,是用户级的轻量级线程。协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文保存在其他地方,切回来恢复。因此,协程能保留上一次调用的状态。
- 在并发编程中,协程与线程类似,每个协程有自己的本地数据,与其他协程共享全局数据和其他资源
- 协程需要用户自己编写调度逻辑,对CPU来说,协程其实是单线程,CPU不需要考虑怎样调度。
python 通过yield提供了对协程的基本支持,但不完全,而使用第三方gevent库是更好的选择。gevent是基于协程的python网络函数库。
-
使用greenlet在libev事件循环顶部提供了一个高级别并发性的api,主要特点:
- 基于libev的快速事件循环,linux上是epoll机制
- 基于greenlet的轻量级执行单元
- API复用了python标准库里的内容
- 支持SSL的协作式sockets
- 可通过线程池或c-ares实现DNS查询
-
通过monkey patching功能使得第三方模块变成协作式。
-
gevent对协程的支持,本质上是greenlet在实现切换工作。greenlet工作流程如下:
-
假如进行访问网络IO操作时,出现阻塞,greenlet就显示切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失,再自动切回原来的代码段继续执行。串行方式
-
有了gevent为我们自动切换协程,就保证greenlet在运行,而不是等待IO。这就是协程比一般多线程高效的原因
-
由于切换是在IO操作时自动完成,所以gevent需要修改python自带的一些标准库,将一些常见的阻塞,如socket、select等地方实现跳转,这一过程启动通过monkey patch完成
-
-
一、简易
以下程序主要用了 gevent 中的 spawn 方法和 joinall 方法,spawn方法 可以看做是用来形成协程,joinall 方法 就是添加这些协程任务,并且启动运行。三个网络操作时并发执行的,而且结束顺序不同,但其实只有一个线程
#!coding:utf-8
from gevent import monkey
monkey.patch_all() # 修改python自带的一些标准库,将一些常见的阻塞实现跳转
import gevent
import urllib3
def run_task(url):
print("Visit --> %s" % url)
try:
http = urllib3.PoolManager() # 由该实例对象处理与线程池的连接以及线程安全的所有细节
response = http.request('GET', url)
data = response.data.decode()
print('%d bytes received from %s.' % (len(data), url))
except Exception as e:
print(e)
if __name__ == '__main__':
urls = ['https://github.com/', 'https://www.csdn.net/', 'http://www.baidu.com/']
# 用来形成协程
greenlets = [gevent.spawn(run_task,url) for url in urls] #主要用了gevent中的spawn 方法和joinall 方法,spawn方法可以看做是用来形成协程,joinall 方法就是添加这些协程任务,并且启动运行。
# 添加这协程任务,并且启动运行
gevent.joinall(greenlets)
二、进阶
gevent 中也提供了对池的支持,对 greenlet 进行 并发管理(限制并发数),就可以使用池,这在处理大量的网络和IO操作时时非常必要的,程序改写如下:
#!coding:utf-8
from gevent import monkey
monkey.patch_all() # 修改python自带的一些标准库,将一些常见的阻塞实现跳转
import gevent
from gevent.pool import Pool
import urllib3
def run_task(url):
print("Visit --> %s" % url)
try:
http = urllib3.PoolManager() # 由该实例对象处理与线程池的连接以及线程安全的所有细节
response = http.request('GET', url)
data = response.data.decode()
print('%d bytes received from %s.' % (len(data), url))
except Exception as e:
print(e)
return 'url:%s ---> finish' % url
if __name__ == '__main__':
urls = ['https://github.com/', 'https://www.csdn.net/', 'http://www.baidu.com/']
pool =Pool(2)
results = pool.map(run_task, urls)
print(results)