Asyncio
Asyncio概念
Sync(同步)和Async(异步)的概念
-
Sync,是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行。
-
Async是指不同操作间可以相互交替执行,如果其中的某个操作被block了,程序并不会等待,而是会找出可执行的操作继续执行。
Asyncio工作原理
Asyncio和其他Python程序一样,是单线程的,它只有一个主线程,但是可以进行多个不同的任务(task),这里的任务,就是特殊的future对象。
这些不同的任务,被一个叫做event loop的对象所控制。
假设任务只有状态:一个预备状态;二是等待状态。所谓的预备状态,是指任务目前空闲,但随时待命准备运行。而等待状态,是指任务已经运行,但正在等待外部的操作完成,比如I/O操作。
在这种情况下,event loop会维护两个任务列表,分别对应这两种状态;并且选取预备状态的一个任务(具体选取哪个任务,和其等待的时间长短、占用资源等等相关),使其运行,一直到这个任务把控制权交还给event loop为止。
当任务把控制权交给event loop时,event loop会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看它们是否完成。
- 如果完成,则将其放到预备状态的列表;
- 如果未完成,则继续放在等待状态的列表。
而原先在预备状态列表的任务位置仍旧不变,因为它们还未运行。
这样,当所有任务被重新放置在合适的列表后,新一轮的循环又开始了:event loop继续从预备状态的列表中选取一个任务使其执行…如此周而复始,直到所有任务完成。
对于Asyncio来说,它的任务在运行时不会被外部的一些因素打断,因此Asyncio内的操作不会出现race condition的情况,这样就不需要担心线程安全的问题了。
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))
async def download_all(sites):
tasks = [asyncio.create_task(download_one(site)) for site in sites]
await asyncio.gather(*tasks)
def main():
sites = [
'https://en.wikipedia.org/wiki/Portal:Arts',
'https://en.wikipedia.org/wiki/Portal:History',
'https://en.wikipedia.org/wiki/Portal:Society',
'https://en.wikipedia.org/wiki/Portal:Biography',
'https://en.wikipedia.org/wiki/Portal:Mathematics',
'https://en.wikipedia.org/wiki/Portal:Technology',
'https://en.wikipedia.org/wiki/Portal:Geography',
'https://en.wikipedia.org/wiki/Portal:Science',
'https://en.wikipedia.org/wiki/Computer_science',
'https://en.wikipedia.org/wiki/Python_(programming_language)',
'https://en.wikipedia.org/wiki/Java_(programming_language)',
'https://en.wikipedia.org/wiki/PHP',
'https://en.wikipedia.org/wiki/Node.js',
'https://en.wikipedia.org/wiki/The_C_Programming_Language',
'https://en.wikipedia.org/wiki/Go_(programming_language)'
]
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 63153 from https://en.wikipedia.org/wiki/Java_(programming_language)
Read 31461 from https://en.wikipedia.org/wiki/Portal:Society
Read 23965 from https://en.wikipedia.org/wiki/Portal:Biography
Read 36312 from https://en.wikipedia.org/wiki/Portal:History
Read 25203 from https://en.wikipedia.org/wiki/Portal:Arts
Read 15160 from https://en.wikipedia.org/wiki/The_C_Programming_Language
Read 28749 from https://en.wikipedia.org/wiki/Portal:Mathematics
Read 29587 from https://en.wikipedia.org/wiki/Portal:Technology
Read 79318 from https://en.wikipedia.org/wiki/PHP
Read 30298 from https://en.wikipedia.org/wiki/Portal:Geography
Read 73914 from https://en.wikipedia.org/wiki/Python_(programming_language)
Read 62218 from https://en.wikipedia.org/wiki/Go_(programming_language)
Read 22318 from https://en.wikipedia.org/wiki/Portal:Science
Read 36800 from https://en.wikipedia.org/wiki/Node.js
Read 67028 from https://en.wikipedia.org/wiki/Computer_science
Download 15 sites in 0.062144195078872144 seconds
这里的Async和await关键词是Asyncio的最新写法,表示这个语句/函数是non-block的,正好对应前面所讲的event loop的概念。如果任务执行的过程需要等待,则将其放入等待状态的列表中,然后继续执行预备状态列表里的任务。
主函数里的asyncio.run(coro)是Asyncio的root call,表示拿到event loop,运行输入的coro,直到它结束,最后关闭这个event loop。
asyncio.gather(*aws, loop=None, return_exception=False),则表示在event loop中运行aws序列的所有任务。
多线程还是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,则需要使用多进程来提高程序运行效率。