Python爬虫--协程

Python爬虫--协程

2020/4/20 更新,根据给社团新生的讲课内容适当进行了补充

协程

  • 协程(coroutine),又称微线程,纤程, 是一种用户级的轻量级的线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存在其他地方,在切换回来时,恢复先前保存的寄存器上下文和栈。因此协程可以保存上一次调用的状态,每次过程重入时,就相当于进入上一次调用的状态。在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源。
  • 协程需要用户自己来编写调度逻辑,对于CPU来说,协程实际是单线程,所以CPU不考虑怎么去调度,切换上下文,这就省去了CPU切换的开销,所以协程在一定程度上又好于多线程。

  • Python使用yield提供了对协程的基本支持

  • 第三方库gevent库是最好的选择,gevent提供了比较完善的协程支持。gevent是一个基于协程的Python的网络函数库,gevent对协程的支持本质是greenlet在实现切换工作。greenlet工作流程如下:假如进行访问网络的I/O操作时,出现阻塞,greenlet就显示切换到另一段没有被阻塞的代码段执行,直到原先的阻塞情况消失以后,再自动切换回原来的代码段继续执行。因此,greenlet是一种合理安排的串行方式。

  • I/O操作耗时,而使程序处于等待状态,有gevent帮我们切换,保证了总有greenlet在运行,而不是等待I/O。

例子1

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

def run_task(url):
print('Visit ---> %s' %(url))
try:
response = requests.get(url)
data = response.content
print('%d byte 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.cnblog.com/']
greenlets = [gevent.spawn(run_task, url) for url in urls]
gevent.joinall(greenlets)

结果

1
2
3
4
5
6
   Visit ---> https://github.com/
Visit ---> https://www.python.org/
Visit ---> http://www.cnblog.com/
2043 byte received from http://www.cnblog.com/
92576 byte received from https://github.com/
48995 byte received from https://www.python.org/
  • 以上主要使用了gevent中的spawn方法和joinall方法,spawn方法可以看做是用来形成协程,而joinall方法就是添加这些任务,而且启动运行,从结果来看,三个操作似乎是并发的,而且结束顺序不同,但实际只有一个线程。

补充关于 yield 的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-

# n = 1
# From Web
def gen(n):
n = 0
while True:
yield n
n += 1


g = gen(n)
print(g) # <generator object gen at 0x00000246E165A7C8>
print(next(g)) # 输出结果为0
print(next(g)) # 输出结果为1
  • 当我们需要下一个 g 的时候,gen 才会继续执行,直到执行到 yield 语句后暂停。这里就可以很明显的猜到其实现就是基于协程的实现,每次执行到 yield 语句的时候保存现场然后切换出去(执行运行的代码的下一条语句),需要计算下一个值的时候恢复上次的计算状态然后接着计算。

优势

  • 协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

  • 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

Author

Ctwo

Posted on

2020-04-20

Updated on

2020-10-25

Licensed under

Comments