Python中的协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程

greenlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from greenlet import greenlet


def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()


def test2():
print(56)
gr1.switch()
print(78)


gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

------------
12
56
34
78

代码从上之下执行

  • 运行到第19行时,执行gr1.switch(),执行test1方法,代码跳到第5行,打印12
  • 接着代码运行到第6行,执行gr2.switch(),执行test2方法,代码跳到第12行,打印56
  • 接着代码运行到第13行,执行gr1.switch(),继续执行test1方法,代码跳到了第7行,打印34
  • 接着代码运行到第8行,执行gr2.switch(),继续执行test2方法,打印78

gevent

gevent是greenlet的更高层次的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import gevent

def foo():
print('Running in foo')
gevent.sleep(0)
print('Explicit context switch to foo again')

def bar():
print('Explicit context to bar')
gevent.sleep(0)
print('Implicit context switch back to bar')

gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
])

------------
Running in foo
Explicit context to bar
Explicit context switch to foo again
Implicit context switch back to bar

gevent中,当遇到gevent.sleep(0)时,就自动切换到其他的方法中去继续执行

协程实例

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from gevent import monkey; monkey.patch_all()
import gevent
import requests

def func(url):
print('GET: %s' % url)
resp = requests.get(url)
data = resp.text
print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
gevent.spawn(func, 'https://www.baidu.com/'),
gevent.spawn(func, 'http://docs.20150509.cn/'),
gevent.spawn(func, 'http://www.20150509.cn/'),
])

------------
GET: https://www.baidu.com/
GET: http://docs.20150509.cn/
GET: http://www.20150509.cn/
1314 bytes received from http://www.20150509.cn/.
227 bytes received from https://www.baidu.com/.
37491 bytes received from http://docs.20150509.cn/.

遇到IO操作自动切换

gevent.joinall内部自带for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from gevent import monkey; monkey.patch_all()
import gevent
import requests

def func(url):
print('GET: %s' % url)
resp = requests.get(url)
data = resp.text
print('%d bytes received from %s.' % (len(data), url))

urls = ['https://www.baidu.com/', 'http://docs.20150509.cn/', 'http://www.20150509.cn/']
glist = []
for i in range(len(urls)):
glist.append(gevent.spawn(func, urls[i]))
print(glist)
gevent.joinall(glist)

------------
[<Greenlet at 0x103396470: func('https://www.baidu.com/')>, <Greenlet at 0x103396638: func('http://docs.20150509.cn/')>, <Greenlet at 0x103396768: func('http://www.20150509.cn/')>]
GET: https://www.baidu.com/
GET: http://docs.20150509.cn/
GET: http://www.20150509.cn/
1314 bytes received from http://www.20150509.cn/.
227 bytes received from https://www.baidu.com/.
37491 bytes received from http://docs.20150509.cn/.

协程的使用场景

  • 爬虫
  • URL监控

参考文档:

http://www.3lian.com/edu/2015/04-30/209255.html

http://www.cnblogs.com/wupeiqi/articles/5040827.html