何为协程,协程就是当程序遇见了IO(输入,输出)操作时,可以选择性的切换到其他任务上,在微观上它就是一个任务一个任务的切换(切换条件一般就是IO操作),在宏观上,我们看到的就是多任务一起执行。
像下面个例子,是一个单任务同步函数。
import time
def fun():
print('涂涂傻')
time.sleep(3)
print('涂涂真的傻')
if __name__ == '__main__':
fun()
结果:
涂涂傻
(等三秒)
涂涂真的傻
其中的time.sleep(3)
就是让当前线程处于一个长达三秒钟的阻塞,就是此时的cpu不是为我而工作。而协程就是将这阻塞的三秒钟的cpu拿去为其他任务工作,合理利用这休眠的时间。
像input()
,request.get(bilibili)
这些,程序都是处于阻塞状态,而一般情况下,当程序进行IO操作时,线程都是会处于阻塞状态。
而上面所讲的都是在单线程下,而异步协程运用于多线程情况下,那么我们接下来说说多任务异步协程。
首先我们需要导入异步协程的模块asyncio
,没安装的,先安装哦
import asyncio
然后我们可以创建一个异步协程函数,记住此时就要在def
前面加上async
,不然你创建的函数就是个正常的函数
async def fun():#async中文意思异步
print('你好呀,我是涂涂')
此时如果像普通函数那样执行,你会发现得不到结果,因为此时是异步协程函数,执行得到的是一个协程对象
g=fun()
print(g)
运行结果:
<coroutine object fun at 0x000001E6AA805140>
如果我们想要执行这个异步协程函数,那么我们得使用它内置的函数asyncio.run(),这个函数需要asyncio模块的支持。
asyncio.run(g)
运行结果:
你好呀,我是涂涂
既然知道了一个异步协程函数怎么构建了,那么接下来我们就多写几个异步协程函数吧
import asyncio
async def fun1():
print('你好呀,我叫涂涂')
await asyncio.sleep(3) #异步操作的代码
print('你好呀,我是你爸爸')
async def fun2():
print('德玛西亚')
await asyncio.sleep(2)
print('艾欧里亚')
async def fun3():
print('哈哈哈')
await asyncio.sleep(4)
print('呜呜呜')
我们用await asyncio.sleep()
模拟阻塞情况,就是此时将这个函数挂起,记住这里不要用time.sleep()
,因为程序只有遇见await
这个关键字就会跳出来,去执行其他程序,但如果是time.sleep()
就不会跳出,这样的话是同步,而不是异步了。
然后我们写一个main()
函数来运行一下
async def main():
tasks=[asyncio.create_task(fun1()),
asyncio.create_task(fun2()),
asyncio.create_task(fun3())]
await asyncio.wait(tasks)
就是将三个函数变成了一个任务列表。
在python3.8版本后,需要加上asyncio.create_task()
来创建任务,以前的版本可以不需要添加。
运行一下看看
t1=time.time()
asyncio.run(main())
t2=time.time()
print("耗时:",t2-t1)
运行结果:
你好呀,我叫涂涂
德玛西亚
哈哈哈哈
艾欧里亚
你好呀,我是你爸爸
呜呜呜
耗时:4.005320310592651
然后我们来说说在如何在爬虫上用到异步协程
import asyncio
async def download(url):
print('准备开始下载')
await asyncio.sleep(2)#模拟网络请求,get()
print('下载完成')
async def main():
urls=[
'baidu.com','sogou.com','bilibili.com'
]
tasks=[]
for url in urls:
d=download(url)
tasks.append(asyncio.create_task(d))
await asyncio.wait(tasks)
这就是一个简单的模拟从网上下载的异步协程框架,接下来我们以一个实际的小例子,从网上随便找几张图片下载。
在进行网络请求的时候,我们需要用到另外一个模块aiohttp
等价于将requests.get()
同步操作———>异步操作aiohttp
import aiohttp
import asyncio
url=['http://kr.shanghai-jiuxin.com/file/bizhi/20211206/oka0yp3aj1t.jpg',
'http://kr.shanghai-jiuxin.com/file/bizhi/20211201/aesbpiexavr.jpg',
'http://kr.shanghai-jiuxin.com/file/bizhi/20211201/r0jwwdpfjjp.jpg']
async def download(url):
#发送请求 s=aiogttp.ClientSession()<==>request
#得到回应
#写入内容
name=url.rsplit('/',1)[1]
async with aiohttp.ClientSession() as session:#等价于request
async with session.get(url) as resp:#等价于resp=request.get()
#请求回来了,写入文件
#可自己学习使用aiofiles模块
with open(name,'wb')as f:
f.write(await resp.content.read())#读取内容也是io操作,需要await挂起,拿取页面源代码resp.text()
print(name,'ok')
async def main():
tasks=[]
for i in url :
tasks.append(asyncio.create_task(download(i)))
await asyncio.wait(tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
如果你使用asyncio.run()
会自动关闭循环,并且调用_ProactorBasePipeTransport.__del__
报错, 而asyncio.run_until_complete()
不会.
运行结果:
oka0yp3aj1t.jpg ok
r0jwwdpfjjp.jpg ok
aesbpiexavr.jpg ok
一个自己用的学习笔记,如有不对,还望大佬予以斧正。