协程(coroutine)又称微线程,纤程,是种用户级别的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时候,将寄存器上下文和栈保存到其他地方,等待切换回来的时候恢复,并从之前保存的寄存器上下文 和 栈继续工作。

并发编程中,协程与 线程类似,每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据及资源池。

协程需要操作员单独写调度逻辑,对CPU来说,协程也就是单线程,因此CPU 不需要考虑怎么调度、切换上下文,省去了CPU开销,所以,协程又在一定程度上好于多线程。

python中实现协程:

python使用yield 提供对协程的基本支持,但是,第三方的 gevent库能更好地提供该服务,gevent有比较完善的协程支持。

geve 是基于协程的python网络函数库,使用greenlet 在libev事件循环顶部提供一个有高级别并发性的API。

特点:

(1)基于libev 的快速事件循环,Linux上的epoll机制。

(2)基于greenlet的轻量级执行单元。

(3)API 复用了python标准库的内容。

(4)支持SSL的协作式sockets。

(5)可以通过线程池或者c-ares 实现DNS查询。

(6)通过 monkey patching功能使得第三方模块编程协作式。

gevent支持协程,其实也可以说是greenlet实现的工作切换。

greenlet工作流程如下:如果访问网路的I/O操作出现阻塞时,greenlet就显式切换到另外一个没有被阻塞的代码段执行,直到原先的阻塞状态消失后,再自动切换会原来的代码段继续处理。 可以说,greenlet是在更合理地安排串行工作方式。

同时,由于IO 操作比较耗时,经常是程序处于等待状态,gevent自动切换协程后,能够保证总有greenlet在运行,而不需要等待IO完成,这是协程比一般多线程效率高的原因。

IO操作是自动完成的,所以gevent 需要修改python的一些自带标准库,将一些常见的阻塞,如:socket、select等地方实现协程跳转,这一过程可以通过monkey patch完成。

如下代码可以显示 gevent的使用流程:(python版本: 3.6  操作系统环境: windows10)from gevent import monkey

monkey.patch_all()

import gevent

import urllib.request

def run_task(url):

print("Visiting %s " % url)

try:

response = urllib.request.urlopen(url)

url_data = response.read()

print("%d bytes received from %s " % (len(url_data), url))

except Exception as e:

print(e)

if __name__ == "__main__":

urls = ["https://stackoverflow.com/", "http://www..com/", "http://github.com/"]

greenlets = [gevent.spawn(run_task, url) for url in urls]

gevent.joinall(greenlets)Visiting https://stackoverflow.com/

Visiting http://www..com/

Visiting http://github.com/

46412 bytes received from http://www..com/

54540 bytes received from http://github.com/

251799 bytes received from https://stackoverflow.com/

gevent的spawn方法可以看做是用来形成协程,joinall 方法相当于添加协程任务并启动运行。由结果可以看到,3个网络请求并发执行,而且结束顺序却不一致,但是却只有一个线程。

gevent还提供池。如果拥有动态数量的 greenlet需要进行并发管理,可以使用池 来处理大量的网络请求 及 IO操作。

如下是 gevent的pool对象,修改如上的多网络请求示例:from gevent import monkey

monkey.patch_all()

from gevent.pool import Pool

import urllib.request

def run_task(url):

print("Visiting %s " % url)

try:

response = urllib.request.urlopen(url)

url_data = response.read()

print("%d bytes reveived from %s " %(len(url_data), url))

except Exception as e:

print(e)

return ("%s read finished.." % url)

if __name__ == "__main__":

pool = Pool(2)

urls = ["https://stackoverflow.com/",

"http://www..com/",

"http://github.com/"]

results = pool.map(run_task, urls)

print(results)

Visiting https://stackoverflow.com/

Visiting http://www..com/

46416 bytes reveived from http://www..com/

Visiting http://github.com/

253375 bytes reveived from https://stackoverflow.com/

54540 bytes reveived from http://github.com/

['https://stackoverflow.com/ read finished..', 'http://www..com/ read finished..', 'http://github.com/ read finished..']    由结果来看,Pool 对象对协程的并发数量进行了管理,先访问前两个,当其中一个任务完成了,再继续执行第三个请求。