Python进阶:并发编程之Asyncio
程序员文章站
2022-03-20 17:38:45
什么是Asyncio 多线程有诸多优点且应用广泛,但也存在一定的局限性: 比如,多线程运行过程容易被打断,因此有可能出现 race condition 的情况;再如,线程切换本身存在一定的损耗,线程数不能无限增加,因此,如果I/O 操作非常 heavy,多线程很有可能满足不了高效率、高质量的需求。 ......
什么是asyncio
多线程有诸多优点且应用广泛,但也存在一定的局限性: 比如,多线程运行过程容易被打断,因此有可能出现 race condition 的情况;再如,线程切换本身存在一定的损耗,线程数不能无限增加,因此,如果i/o 操作非常 heavy,多线程很有可能满足不了高效率、高质量的需求。
因此,asyncio 应运而生。
sync(同步) vs async(异步)
所谓 sync,是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行。而 async 是指不同操作间可以相互交替执行,如果其中的某个操作被 block 了,程序并不会等待,而是会找出可执行的操作继续执行。
asyncio 工作原理
asyncio 和其他 python 程序一样,是单线程的,它只有一个主线程,但是可以进行多个不同的任务(task),这里的任务,就是特殊的 future 对象。这些不同的任务,被一个叫做 event loop 的对象所控制。event loop 会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看他们是否完成。如果完成,则将其放到预备状态的列表; 如果未完成,则继续放在等待状态的列表。
asyncio 用法
import asyncio import aiohttp import time async def download_one(url): async with aiohttp.clientsession() as session: async with session.get(url) as resp: print('read {} from {}'.format(resp.content_length, url)) #text_len = await resp.text() #print('read {} from {}'.format(len(text_len), url)) #print('len(text_len)',len(text_len)) async def download_all(sites): tasks = [asyncio.create_task(download_one(site)) for site in sites] await asyncio.gather(*tasks) def main(): sites = [ 'https://www.baidu.com/', 'https://pypi.org/', 'https://www.sina.com.cn/', 'https://www.163.com/', 'https://news.qq.com/', 'http://www.ifeng.com/', 'http://www.ce.cn/', 'https://news.baidu.com/', 'http://www.people.com.cn/', 'http://www.ce.cn/', 'https://news.163.com/', 'http://news.sohu.com/' ] start_time = time.perf_counter() asyncio.run(download_all(sites)) end_time = time.perf_counter() print('download {} sites in {} seconds'.format(len(sites), end_time - start_time)) if __name__ == '__main__': main() # 输出 read 227 from https://www.baidu.com/ read none from http://www.ce.cn/ read none from http://www.ce.cn/ read 38914 from http://www.people.com.cn/ read none from https://news.qq.com/ read none from https://news.163.com/ read none from https://www.163.com/ read 129268 from https://www.sina.com.cn/ read none from http://www.ifeng.com/ read none from https://news.baidu.com/ read none from http://news.sohu.com/ read 4293 from https://pypi.org/ download 12 sites in 0.7875643999999999 seconds
主函数里的 asyncio.run(coro) 是 asyncio 的 root call,表示拿到 event loop,运行输入的 coro,直到它结束,最后关闭这个 event loop。
事实上,asyncio.run() 是 python3.7+ 才引入的,相当于老版本的以下语句:
loop = asyncio.get_event_loop() try: loop.run_until_complete(coro) finally: loop.close()
asyncio 有缺陷吗?
首先有兼容性问题,如asyncio 与 requests库不兼容,因此上例使用的是aiohttp库(上例aiohttp库出现content_length为none,暂不知原因).另外是任务调度的问题,你需要 await 一系列的操作,就得使用 asyncio.gather();如果只是单个的 future,或许只用 asyncio.wait() 就可以了。那么,对于你的 future,你是想要让它 run_until_complete() 还是 run_forever() 呢?
asyncio 还是 多线程?
可以遵循以下伪代码的规范:
if io_bound: if io_slow: print('use asyncio') else: print('use multi-threading') else if cpu_bound: print('use multi-processing')
- 如果是 i/o bound,并且 i/o 操作很慢,需要很多任务 / 线程协同实现,那么使用 asyncio 更合适。
- 如果是 i/o bound,但是 i/o 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。
- 如果是 cpu bound,则需要使用多进程来提高程序运行效率。
参考
极客时间《python核心技术与实战》专栏
上一篇: 不打扰它们休息了