C10K 瓶颈,也就是同时连接到服务器的客户达到了一万个
事件循环
事件循环启动一个统一的调度器,让调度器来决定一个时刻去运行哪个任务,于是省却了多线程中启动线程、管理线程、同步锁等各种开销
回调地狱(callback hell
这种工具完美地继承了事件循环的优越性,同时还能提供 async / await 语法糖,解决了执行性和可读性共存的难题
Python 2 使用生成器实现协程 --老方法
Python 3.7 提供了新的基于 asyncio 和 async / await 的方法
代码示例1
import asyncio
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('OK {}'.format(url))
async def main(urls):
for url in urls:
await crawl_page(url)
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
########## 输出 ##########
crawling url_1
OK url_1
crawling url_2
OK url_2
crawling url_3
OK url_3
crawling url_4
OK url_4
Wall time: 10 s
这里的 crawl_page 和 main 都是异步函数, 调用异步函 便可得到一个协程对象(coroutine object)
协程的执行方式
通过 await 调用。
await 执行的效果,和 Python 正常执行是一样的,也就是说程序会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续,而这也是 await 的字面意思。
await 是同步调用,因此 crawl_page(url) 在当前的调用结束之前,是不会触发下一次调用的。
代码中
await asyncio.sleep(sleep_time) 会在这里休息若干秒
await crawl_page(url) 则会执行 crawl_page() 函数
asyncio.create_task() 来创建任务
asyncio.run
让Python 的协程接口变得非常简单,不用去理会事件循环怎么定义和怎么使用的问题
好的编程规范是,asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次asyncio.run。
代码示例2
import asyncio
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('OK {}'.format(url))
async def main(urls):
tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
for task in tasks:
await task
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
########## 输出 ##########
crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 3.99 s
通过 asyncio.create_task 来创建任务。任务创建后很快就会被调度执行,代码也不会阻塞在任务这里。
所以,我们要等所有任务都结束才行,用for task in tasks: await task 即可
代码示例3
import asyncio
async def worker_1():
print('worker_1 start')
await asyncio.sleep(1)
print('worker_1 done')
async def worker_2():
print('worker_2 start')
await asyncio.sleep(2)
print('worker_2 done')
async def main():
print('before await')
await worker_1()
print('awaited worker_1')
await worker_2()
print('awaited worker_2')
%time asyncio.run(main())
########## 输出 ##########
before await
worker_1 start
worker_1 done
awaited worker_1
worker_2 start
worker_2 done
awaited worker_2
Wall time: 3 s
代码示例4 解密协程运行时
import asyncio
async def worker_1():
# step4 worker_1开始运行
print('worker_1 start')
# step5 运行到await asyncio.sleep(1)时 事件调度器将worker_1 任务切出
await asyncio.sleep(1)
# step9 1秒后 调调度器将控制权交给worker_1, 任务完成 从事件循环中退出
print('worker_1 done')
# step9 worker_1 任务完成 从事件循环中退出
async def worker_2():
# step7 worker_2开始运行
print('worker_2 start')
# step8 当前任务切出 事件调度器将worker_2 任务切出
await asyncio.sleep(2)
# step11 2秒后 调度器将控制权交给worker2 任务完成 从事件循环中退出
print('worker_2 done')
async def main():
# step2 创建task1 task2 并且进入循环时间等待
task1 = asyncio.create_task(worker_1())
task2 = asyncio.create_task(worker_2())
print('before await')
# step3 await task1执行时 代表用户选择从当前的主任务中切出,事件调度器开始调度 worker_1这个任务
await task1
print('awaited worker_1')
# step6 await task2时 事件调度器开始调度 worker_2这个任务
await task2
print('awaited worker_2')
## end 协程任务全部结束 事件循环结束 调度器结束
# step1 程序进入 main() 函数,事件循环开启
%time asyncio.run(main())
########## 输出 ##########
before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
Wall time: 2.01 s
step1-8
以上所有事件 step1 -- step8 的运行时间,都应该在 1ms 到 10ms 之间,甚至可能更短,事件调度器从这个时候开始暂停调度
step9
一秒钟后,worker_1 的 sleep 完成,事件调度器将控制权重新传给 task_1,输出 'worker_1 done',task_1 完成任务,从事件循环中退出
step10
await task1 完成,事件调度器将控制器传给主任务,输出 'awaited worker_1',·然后在 await task2 处继续等待;
step11
两秒钟后,worker_2 的 sleep 完成,事件调度器将控制权重新传给 task_2,输出 'worker_2 done',task_2 完成任务,从事件循环中退出;
step12
主任务输出 'awaited worker_2',协程全任务结束,事件循环结束
代码示例5 - 协程任务限定运行时间
import asyncio
async def worker_1():
await asyncio.sleep(1)
return 1
async def worker_2():
await asyncio.sleep(2)
return 2 / 0
async def worker_3():
await asyncio.sleep(3)
return 3
async def main():
task_1 = asyncio.create_task(worker_1())
task_2 = asyncio.create_task(worker_2())
task_3 = asyncio.create_task(worker_3())
await asyncio.sleep(2)
task_3.cancel()
res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)
print(res)
%time asyncio.run(main())
########## 输出 ##########
[1, ZeroDivisionError('division by zero'), CancelledError()]
Wall time: 2