安装:pip install gevent
协程定义
又名微线程,纤程。在不开辟线程的基础上完成多任务,即在单线程情况下完成多任务,多个任务按照一定顺序交替执行。可认为看到yield关键字就是协程。本质为是个单线程。
协程是实现多任务的一种方法。
缺点:无法利用多核资源
优点:提高执行效率————–无需线程的切换开销。
高可用+高扩展性+低成本——一个CPU可支持上万个协程(高并发);
不需要多线程的锁制作——–如生产者-消费者问题。传统方法需要使用两个线程,但如果使用在一个线程中使用关键字yield开辟协程便可解决。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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 import time
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
time.sleep(1)
r = '200 OK'
def produce(c):
c.next()
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
if __name__=='__main__':
c = consumer()
produce(c)
执行结果:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
注:[代码来源参考](https://www.liaoxuefeng.com/wiki/897692888725344/923057403198272)
协程使用
使用安装:cmd下执行命令:pip install greenlet
使用一个简单例子解释greenlet用法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import time,greenlet
def work1():
for i in range(5):
print('---work1-----')
time.sleep(0.5)
##切换协程
g2.switch()
def work2():
for i in range(5):
print('---work2-----')
time.sleep(1)
g1.switch()
if __name__ == "__main__":
g1=greenlet.greenlet(work1)
g2=greenlet.greenlet(work2)
g1.switch()
输出:
---work1-----
---work2-----
---work1-----
---work2-----
greenlet可以实现协程,但需要人工切换,很多场景不适用。因此更多时候我们使用一个更强大的库————Gevent。
Gevent
使用安装:在cmd中输入命令:pip install gevent
Gevent内部封装Greenlet,其原理相当于一个Greenlet遇到IO操作【网络、文件操作】时,自动切换到其他Greenlet,等到这个IO操作完成,在适当时候切换回来执行。
例子使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import gevent
def work(n):
for i in range(n):
##获取当前协程的属性
print(gevent.getcurrent(),i)
g1=gevent.spawn(work,2)
g2=gevent.spawn(work,3)
g1.join()
g2.join()
输出:
<Greenlet at 0x2177fb5ae18: work(2)> 0
<Greenlet at 0x2177fb5ae18: work(2)> 1
<Greenlet at 0x21700338048: work(3)> 0
<Greenlet at 0x21700338048: work(3)> 1
<Greenlet at 0x21700338048: work(3)> 2
由例子可得出程序是先完全执行完g1对象,再执行g2--------原因是在此程序中无IO操作,无需进行协程切换
注:spawn:引发、导致、造成
例子改写1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import gevent,time
def work(n):
for i in range(n):
##获取当前协程的属性
print(gevent.getcurrent(),i)
gevent.sleep(1) ##让协程睡眠
g1=gevent.spawn(work,2)
g2=gevent.spawn(work,3)
##阻塞,程序资源不被释放
g1.join()
g2.join()
输出:
<Greenlet at 0x2177fb5ad08: work(2)> 0
<Greenlet at 0x2177fb5abf8: work(3)> 0
<Greenlet at 0x2177fb5ad08: work(2)> 1
<Greenlet at 0x2177fb5abf8: work(3)> 1
<Greenlet at 0x2177fb5abf8: work(3)> 2
可以发现这次实现了协程切换。。。遗憾的是,如果换成time.sleep(1)从本质上看也拥有了IO操作,但并不能触发协程,原因是Gevent无法识别这类IO操作。
解决方法:打补丁---------from gevent import monkey---------官方建议是程序开始就开始打补丁
例如在以上例子在程序开头加上:1
2from gevent import monkey
monkey.patch_all()
这样的话把上面例子的gevent.sleep(1)改为time.sleep(1)也是同样结果
想让程序处于阻塞状态,切换协程的两种方法:1
2
3
4
5
6
7
8if __name__ == "__main__":
g1=gevent.spawn(work1)
g2=gevent.spawn(work2)
while True:
print("hello word")
time.sleep(1)
#g1.join()
#g2.join()
①使用循环,在循环中进行IO操作 ②join函数阻塞
Gevent+爬虫 实战训练
爬虫流程: 找到资源链接-——获取资源————将资源数据写入到本地文件
1 | from gevent import monkey |
本次每次写入1024个字节,img_url2.jpg图片最大,根据协程转换,一般会最后下载成功。(每次运行的结果可能不同)
实战升级————爬取360网络图片
1 | from gevent import monkey |
本部分主要偏于爬虫方面,需要使用到一定的html和网络通信协议这方面的知识
最后更新: 2019年08月22日 15:26
原始链接: https://LiYuanSh.github.io/2019/04/20/python——Gevent协程/