为什么使用gevent
- Python通过yield提供了对协程的基本支持,但是不完全。比如python的yield虽然提供了对协程的支持,但是需要用send手动发送数据(比如io操作时的切换,需要发送 “耗时操作完成”告诉程序可以继续往下走),才能改变程序的执行流程,,而第三方的gevent为Python提供了比较完善的协程支持。
gevent的优点
- gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch(猴子补丁)完成。
案例:爬取51job
- 代码如下
import gevent
from gevent import monkey
# 将【标准库-阻塞IO实现】替换为【gevent-非阻塞IO实现】
monkey.patch_all()
import requests
from lxml import etree
import time
def get_51info(page):
url = f"https://search.51job.com/list/020000,000000,0000,00,9,99,python,2,{page}.html?lang=c&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare="
resp = requests.get(url)
html_str = resp.content.decode("gbk")
root = etree.HTML(html_str)
job_list = root.xpath('//div[@id="resultList"]/div[@class="el"]')
for job in job_list:
position = job.xpath('./p/span/a/@title')
company_name = job.xpath('./span[@class="t2"]/a/@title')
salary = job.xpath('./span[@class="t4"]/text()')
# 空列表 空字典 并不等None
if salary != []:
print(position[0],company_name[0],salary[0])
if __name__ == '__main__':
# 不使用协程
# start = time.time()
# # print(start)
# for i in range(1,51):
# get_51info(i)
# end = time.time()
# # print(end)
# #over,耗时41.33733057975769秒 不使用协程花费了41秒
# print(f"over,耗时{(end - start)}秒")
# 使用协程
start = time.time()
# 协程传递参数,(方法名,参数。。。)
gevent.joinall(
[gevent.spawn(get_51info,i) for i in range(1,51)]
)
end = time.time()
#over,耗时13.129491090774536秒 使用协程花费了19秒
print(f"over,耗时{(end - start)}秒")
经过测试,不使用协程时(单线程)爬取50页岗位信息花费41秒,使用协程时(单线程中实现了多个协程对象)花费了13秒,
- 因为GIL锁的存在,python的多线程一个时刻只有一个线程执行,而单线程中实现了多协程,协程是程序级别的,协程的切换效率很高,不受GIL锁的影响,所以可以极大的提升执行效率
- 附:在Python多线程下,每个线程的执行方式:
1、获取GIL
2、执行代码直到sleep或者是python虚拟机将其挂起。
3、释放GIL
可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。