异步IO:asyncio
asyncio
aysncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模式就是一个消息循环。我们从asyncio模板中直接获取一个Eventloop(事件循环)的引用,然后把需要执行的协程扔到Eventloop中执行,就实现了异步IO。
用asyncio实现Hello world代码如下
async_hello.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # asyncio start import asyncio # 装饰器装饰的函数为协程函数 @asyncio .coroutine def hello(): print ( 'Hello world!' ) r = yield from asyncio.sleep( 1 ) print ( 'Hello again!' ) # 获取Eventloop事件循环 loop = asyncio.get_event_loop() # 执行协程,协程函数不能直接执行,需要放入事件循环才能执行 loop.run_until_complete(hello()) # 关闭事件循环 loop.close() # asyncio end |
输出如下
1 2 | Hello world! Hello again! |
解析:
运行事件循环,事件循环绑定的协程函数是hello()该协程函数首先打印 “Hello world! ” 然后遇到yield from asyncio.sleep(1)模拟的IO耗时操作,此时如果事件循环绑定了其他协程则会去执行其他协程,本次只绑定了一个协程所以等待1秒后输出“hello again!”
@asyncio.coroutine
把一个generator标记为coroutine类型,然后,我们就把这个coroutine
扔到EventLoop
中执行。
hello()
会首先打印出Hello world!
,然后,yield from
语法可以让我们方便地调用另一个generator
。由于asyncio.sleep()
也是一个coroutine
,所以线程不会等待asyncio.sleep()
,而是直接中断并执行下一个消息循环。当asyncio.sleep()
返回时,线程就可以从yield from
拿到返回值(此处是None
),然后接着执行下一行语句。
把asyncio.sleep(1)
看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop
中其他可以执行的coroutine
了,因此可以实现并发执行。
我们用Task封装两个coroutine
试试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 封装两个task任务 start import asyncio import threading # 装饰器装饰的函数为协程函数 @asyncio .coroutine def hello(): print ( 'Hello world! %s' % threading.currentThread() ) yield from asyncio.sleep( 1 ) print ( 'Hello again! %s' % threading.currentThread()) # 获取Eventloop事件循环 loop = asyncio.get_event_loop() # 执行协程,协程函数不能直接执行,需要放入事件循环才能执行 tasks = [hello(),hello()] loop.run_until_complete(asyncio.wait(tasks)) # 关闭事件循环 loop.close() # 封装两个task任务 end |
输出如下
1 2 3 4 5 | Hello world! <_MainThread(MainThread, started 18168 )> Hello world! <_MainThread(MainThread, started 18168 )> 等待约 1 秒 Hello again! <_MainThread(MainThread, started 18168 )> Hello again! <_MainThread(MainThread, started 18168 )> |
由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。所以输出两个Hello world!几乎是同时的,然后两个协程同时等待1秒输出两个Hello again! 。如果不是并发则应该前后两次输出,类似下面这样
1 2 3 4 5 6 | Hello world! <_MainThread(MainThread, started 18168 )> 等待约 1 秒 Hello again! <_MainThread(MainThread, started 18168 )> Hello world! <_MainThread(MainThread, started 18168 )> 等待约 1 秒 Hello again! <_MainThread(MainThread, started 18168 )> |
如果把asyncio.sleep()换成真正的IO操作,则多个coroutine是由同一个线程并发执行的。
我们用asyncio的异步网络连接来获取sina,sohu和163的网站首页
async_wget.py
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 43 44 45 46 47 48 49 50 | import asyncio @asyncio .coroutine def wget(host): print ( 'wget %s...' % host) # 创建TCP客户端并连接服务器,或者说创建一个TCP连接对象 # open_connection接收两个参数:主机名和端口号 # connect是协程,这步仅创建协程对象,立即返回,不阻塞 connect = asyncio.open_connection(host, 80 ) # 运行协程连接服务器,这步是阻塞操作,释放CPU # 连接创建成功后,asyncio.open_connection方法返回的就是读写对象 # 读写对象分别为 StreamReader和StreamWriter实例 # 它们也是协程对象,底层调用Socker模块的send和recv方法实现读写 reader,writer = yield from connect # heade是发送给服务器的消息,意为获取页面的header信息 # 它的格式是固定的 header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host # 给服务器发消息,注意消息是二进制的,本次使用utf-8编码 writer.write(header.encode( 'utf-8' )) # writer.drain()是一个与底层IO输入缓冲区交互流量控制方法,需要配合writer.write使用 # 当缓冲区达到上限时,drain()阻塞,待缓冲区回落到下限时,写操作恢复 # 当不需要等待时,drain()会立即返回,假如上面的消息内容较少,不会阻塞 # 这就是一个控制消息数据量的控制阀 yield from writer.drain() # 给服务器发送消息后,就等着读取服务器返回来的消息 while True : # 读取数据是阻塞操作,释放CPU # reader相当于一个水盆,服务器发来的数据是水流 # readline表示读取一行,以\n作为换行符 # 如果在出现\n之前,数据流出现EOF(End of File文件结束符)也会返回 # 相当于出现\n或EOF时,拧上水龙头,line就是这盆水 line = yield from reader.readline() # 数据接收完毕,会返回空字符串\r\n,退出while循环,结束数据接收 if line = = b '\r\n' : break # 接收数据是二进制,转换为UTF-8格式并打印 # rstrip()方法删掉字符串结尾就是右边的空白字符,也就是\n print ( '%s header > %s' % (host,line.decode( 'utf-8' ).rstrip())) # 关闭数据流,可以省略 writer.close() yield from writer.wait_closed() hosts = [ '192.168.1.100' ] hosts = [ 'www.sina.com.cn' , 'www.sohu.com' , 'www.163.com' ] # 创建task任务 tasks = [wget(host) for host in hosts ] # 创建事件循环 loop = asyncio.get_event_loop() # 运行事件循环 loop.run_until_complete(asyncio.wait(tasks)) |
执行结果如下
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | wget www.sohu.com... wget www. 163.com ... wget www.baidu.com... www.sohu.com header > HTTP / 1.1 307 Temporary Redirect www.sohu.com header > Content - Type : text / html www.sohu.com header > Content - Length: 180 www.sohu.com header > Connection: close www.sohu.com header > Server: nginx www.sohu.com header > Date: Fri, 29 Oct 2021 09 : 18 : 16 GMT www.sohu.com header > Location: https: / / www.sohu.com / www.sohu.com header > FSS - Cache: from 4094608.6191770 . 5431472 www.sohu.com header > FSS - Proxy: Powered by 4356756.6716062 . 5693624 www. 163.com header > HTTP / 1.1 301 Moved Permanently www. 163.com header > Date: Fri, 29 Oct 2021 09 : 18 : 16 GMT www. 163.com header > Content - Length: 0 www. 163.com header > Connection: close www. 163.com header > Server: web cache www. 163.com header > Location: https: / / www. 163.com / www. 163.com header > Cache - Control: no - cache,no - store,private www. 163.com header > cdn - user - ip: 116.25 . 237.202 www. 163.com header > cdn - ip: 183.47 . 233.150 www. 163.com header > X - Cache - Remote: MISS www. 163.com header > cdn - source: baishan www.baidu.com header > HTTP / 1.0 200 OK www.baidu.com header > Accept - Ranges: bytes www.baidu.com header > Cache - Control: no - cache www.baidu.com header > Content - Length: 14615 www.baidu.com header > Content - Type : text / html www.baidu.com header > Date: Fri, 29 Oct 2021 09 : 18 : 16 GMT www.baidu.com header > P3p: CP = " OTI DSP COR IVA OUR IND COM " www.baidu.com header > Pragma: no - cache www.baidu.com header > Server: BWS / 1.1 www.baidu.com header > Set - Cookie: BAIDUID = 00D297D9A818428E9A332BB01C2BA52E :FG = 1 ; expires = Thu, 31 - Dec - 37 23 : 55 : 55 GMT; max - age = 2147483647 ; path = / ; domain = .baidu.com www.baidu.com header > Set - Cookie: BIDUPSID = 00D297D9A818428E9A332BB01C2BA52E ; expires = Thu, 31 - Dec - 37 23 : 55 : 55 GMT; max - age = 2147483647 ; path = / ; domain = .baidu.com www.baidu.com header > Set - Cookie: PSTM = 1635499096 ; expires = Thu, 31 - Dec - 37 23 : 55 : 55 GMT; max - age = 2147483647 ; path = / ; domain = .baidu.com www.baidu.com header > Set - Cookie: BAIDUID = 00D297D9A818428E1A7073DA9DEB891D :FG = 1 ; max - age = 31536000 ; expires = Sat, 29 - Oct - 22 09 : 18 : 16 GMT; domain = .baidu.com; path = / ; version = 1 ; comment = bd www.baidu.com header > Traceid: 1635499096061364455412067686187092680246 www.baidu.com header > Vary: Accept - Encoding www.baidu.com header > X - Frame - Options: sameorigin www.baidu.com header > X - Ua - Compatible: IE = Edge,chrome = 1 PS D:\learn - python3\函数式编程> & C: / ProgramData / Anaconda3 / python.exe d: / learn - python3 / 异步IO / async_wget.py d: / learn - python3 / 异步IO / async_wget.py: 4 : DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8 , use "async def" instead def wget(host): wget www. 163.com ... wget www.sohu.com... wget www.sina.com.cn... www.sohu.com header > HTTP / 1.1 307 Temporary Redirect www.sohu.com header > Content - Type : text / html www.sohu.com header > Content - Length: 180 www.sohu.com header > Connection: close www.sohu.com header > Server: nginx www.sohu.com header > Date: Fri, 29 Oct 2021 09 : 19 : 33 GMT www.sohu.com header > Location: https: / / www.sohu.com / www.sohu.com header > FSS - Cache: from 4160145.6322843 . 5497010 www.sohu.com header > FSS - Proxy: Powered by 4356756.6716062 . 5693624 www. 163.com header > HTTP / 1.1 301 Moved Permanently www. 163.com header > Date: Fri, 29 Oct 2021 09 : 19 : 33 GMT www. 163.com header > Content - Length: 0 www. 163.com header > Connection: close www. 163.com header > Server: web cache www. 163.com header > Location: https: / / www. 163.com / www. 163.com header > Cache - Control: no - cache,no - store,private www. 163.com header > cdn - user - ip: 116.25 . 237.202 www. 163.com header > cdn - ip: 183.47 . 233.148 www. 163.com header > X - Cache - Remote: MISS www. 163.com header > cdn - source: baishan www.sina.com.cn header > HTTP / 1.1 302 Found www.sina.com.cn header > Server: Tengine www.sina.com.cn header > Date: Fri, 29 Oct 2021 09 : 19 : 33 GMT www.sina.com.cn header > Content - Type : text / html www.sina.com.cn header > Content - Length: 242 www.sina.com.cn header > Connection: close www.sina.com.cn header > Location: https: / / www.sina.com.cn / www.sina.com.cn header > X - DSL - CHECK: 5 www.sina.com.cn header > X - Via - CDN: f = alicdn,s = cache5.cn1366,c = 116.25 . 237.202 ; www.sina.com.cn header > Via: cache5.cn1366[, 0 ] www.sina.com.cn header > Timing - Allow - Origin: * www.sina.com.cn header > EagleId: 0e1d289916354991731167720e |
可见3个连接由一个线程通过coroutine并发完成。
小结
asyncio
提供了完善的异步IO支持;
异步操作需要在coroutine
中通过yield from
完成;
多个coroutine
可以封装成一组Task然后并发执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2020-10-29 Python文件IO操作