尽管协程与异步编程技术涉略之广泛,使其理论名称稍显艰涩,然而在实际操作环节,我们无需对其深度挖掘以理解其复杂特性。本篇文章旨在详尽阐述如何运用此类技术提升程序运行效率,使程序开发人员在编码过程中体验到更多的愉悦感。
什么是协程?
虽公认协程为尖端科技,然其根源可追溯至早期。简言之,协程非传统进程或线程,更似子例程或无返回值函数调用。设想一程序中可包含多协程,犹如一进程中含多线程。然而,协程切换无需依赖操作系统,自控完成。
想象您在繁华的社交场所,专业侍者悉心斟酒,善于言辞的人们热情谈话,共同营造出愉快和谐之氛围。实际上,协程平台的运作方式,如同这样一个场景——所有参与者均可以自己的节奏推进工作,无需依赖系统。
协程的意义
为何采用协程技术?这主要源于其能在处理耗时任务上发挥出色并行能力,以此显著提升代码效率。以爬虫程序为例,该功能可使我们同时进行多项数据请求,获取丰富多元的数据类型,如数据、图片及视频等。因此,单独运行的爬虫变为模拟多线程的运行模式,实现多个任务的同步并行执行。
实现协程的方法
当今时代,协程以其出色的性能在多种技术领域广泛应用。其中,sync与await关键字作为主流且高效的实现手段,必将成为您编程道路上的得力助手,使您的代码更具独特魅力。
单条时间启动vs多个事件启动
众所周知,程序有单线程起动及多线程触发优先级双模式运转。前者为经典程序设计法,子任务须待上一任务完成后方能运行;后者则充分展现了协程技术优势,允许多任务并发启动,大幅提升代码运行效率,实现多任务并行处理。
await关键字的重要性
探讨协程,不得不提及await关键字。如同协程的静态冻结器,其能够通过执行耗能操作期间的暂停,确保操作完毕后再恢复进程。此举有效解决了因长期操作引发的代码堵塞问题,使得其他任务得以高效执行。
- 时间循环可理解为死循环,一直检查一些代码执行情况,并做出相应处理
- 事件就是指我们在爬取数据过程,不过该事件通常是以函数调用的形式出现
- 就像我们想爬取多个视频,那么一个事件就可以是将视频url传入函数,进行爬取的过程
# 每个事件均有他自己的状态已完成:finished,未完成:pending
任务列表=[任务1,任务2,任务3....]
while True:
可执行的任务列表=任务列表中取出可以被运行的任务
已完成任务列表=任务列表中取出已经运行完成的任务
for 就绪任务 in 可执行的任务列表:
执行就绪任务
for 已完成任务 in 已完成任务列表:
在任务列表移除已完成任务
直到任务列表全部完成跳出循环
回调函数的必要性
形如
async def 函数名:
函数体
就是一个协程函数
协程函数的调用就是一个协程对象即 函数名()
但是调用协程函数得到的对象并不会直接执行(因为事件并未加载进事件循环的任务列表中去)
需要用到特定函数才能实现
在协程组合模型里,回调函数起着至关重要的作用。例如,一项任务在完美执行之后,须向其他关联任务发送通知,此时,回调函数就如同使者,准确无误地将信息传达至各个任务,从而提升整体程序的运行效率。
基于协程的数据爬取
import asyncio
async def request_1(url):
print('正在请求的url是',url)
print('请求成功,',url)
return url
async def request_2(url):
print('正在请求的url是', url)
print('请求成功,', url)
return url
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 基于loop创建了一个task_1任务对象(注意创建任务loop.create_task必须在事件循环创建以后)
task_1 = loop.create_task(request_1("https://www.taobao.com/"))
# 基于loop创建了一个task_2任务对象,现在loop事件循环的时间里就存在了两个任务
task_2=loop.create_task(request_2("https://www.baidu.com/?"))
print(task_2)#查看一下状态
# 启动事件循环loop,可以传入task对象,也可以是协程对象
# 只不过传入协程对象该函数会自动增加一步在loop事件循环中注册该协程对象
loop.run_until_complete(task_1)
print(task_2)#结果 finished
print(task_1)#结果 finished
#说明loop.run_until_complete(task_1)在运行task_1时启动了事件循环loop中所有的task事件对象
# 那么是不是咋以后的启动事件循环时只启动一个就可以运行所有了呢,当然不是
# 因为在task_1完成后极短时间内,主程序还没来得及print(task_2),task_2也随即完成了,使我们看到print(task_2)时task_2已完成
#当task_2耗时更长一点时(例如在request_2添加await asyncio.sleep(0.01),就会发现虽然是0.01秒print(task_2)结果是pending,即未完成)
#说明可能某些原因导致在其他任务未完成的情况下就执行了下面代码
#所以我们需要保证所有任务都要完成才能继续运行那么该怎么做呢?
本文深度解析了协程策略在数据采集方面的高效性,其核心在于可高效并行获取多页面信息。同时,借助协程特性,即便在网页响应滞后时,爬虫仍能持续进行其他操作,从而提升整体运行效率。
虽然初次接触协程异步编程稍显复杂,但是在掌握其核心理论和实践后,便可领略到其中的独特魅力及其对提升代码效率的巨大潜力。故而,值得我们挺身而出,大胆踏入这个充满挑战的领域,去探索协程异步编程的奥秘!
import asyncio
async def request_1(url):
print('正在请求的url是',url)
print('请求成功,',url)
return url
async def request_2(url):
print('正在请求的url是', url)
print('请求成功,', url)
return url
#******************方法一****************
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
#实际上不用使用loop.create_task也可以运行,不过还是建议加上
task_list = [
loop.create_task(request_1("https://www.baidu.com/?"))
,loop.create_task(request_1("https://www.taobao.com/"))
]
#上面已提到在run_until_complete中要传入任务对象和协程对象
#所以loop.run_until_complete(task_list)是错误的,因为task_list是一个列表
#而添加上asyncio.wait就可以实现传入任务列表了
#这样一来就可以实现loop里面所有任务全部实现完成后才进行下一步
done,pending=loop.run_until_complete(asyncio.wait(task_list))
#done是已完成任务的集合,pending是未完成任务的集合
#******************方法二****************
task_list = [
request_1("https://www.baidu.com/?")
,request_1("https://www.taobao.com/")
]
#这句代码的含义是创建事件循环并运行
#相当于loop = asyncio.get_event_loop()和loop.run_until_complete(asyncio.wait(task_list))的集合
#不过要特别注意的是因为创建任务必须在事件循环创建以后,所以在创建task_list时只能写成协程对象,不能是任务对象
done,pending=asyncio.run(asyncio.wait(task_list))