Python 异步编程
关于 Python 异步编程 的科普文章,涵盖 asyncio, await, 事件循环 等关键词。
Python 异步编程:别傻傻地排队了,这玩意儿才是摸鱼神器
哎,构建脚本又卡在 89% 不动了...这破公司的 Jenkins 服务器估计是上个世纪的古董,慢得我想把显示器吃了。趁着这空档,给你们这帮刚入坑的小白扯扯 Python 的 `asyncio`。别一天天只知道写同步代码,那玩意儿跑起来跟老太太过马路似的,看着我都替 CPU 着急。
老实说,当年为了搞懂这个 `asyncio` 和 `event loop`,我发际线至少后移了两厘米...这破文档写得跟天书一样,谁看谁迷糊。
为什么你需要异步?(除非你想被用户喷死)
听着,同步编程(Synchronous)就是一根筋。你去买早点,前面那哥们儿在那纠结是要豆腐脑还是豆浆,你就得在后面干站着。这就叫 Blocking(阻塞)。在代码里,就是你的程序傻傻地等着网络请求返回,CPU 就在那闲得抠脚,这资源浪费得简直令人发指!!!
而异步编程(Asynchronous)呢?就像你在早高峰的地铁上试图吃韭菜盒子...啊呸,不对。应该说,就像你去餐馆点餐,点完你就回座玩手机去了,服务员做好了会喊你。这就叫 Non-blocking(非阻塞)。Python 的 `asyncio` 就是干这个的,特别适合那种 IO 密集型的任务,比如爬虫啊、Web 服务啊。
别跟我提 Java 的多线程,那玩意儿起一个线程吃一坨内存,还要处理各种锁,写起来心态崩了想砸键盘。Python 的协程(Coroutine)才是亲儿子,单线程里玩并发,切换开销小得一匹,yyds!
核心概念:这三个玩意儿搞不懂就别写了
- Event Loop(事件循环):你可以把它想象成一个不知疲倦的打工人(或者说就是苦逼的 PM)。它在一个死循环里不断地轮询:”哎,那个网络请求好了没?好了?行,回调函数跑起来!“
- async:这就好比给函数贴个条,“这货是个协程,别直接调用它,得安排进日程表”。
- await:这就是精髓了。意思是“老子要在这里等一会儿(比如等数据库返回),Event Loop 你先去忙别的,别在我这死磕”。
代码演示:从某个烂尾项目里扒出来的
来来来,上代码。这代码风格你们凑合看,变量名有点乱,毕竟是从我那个跑路的前同事留下的屎山里扒出来的...稍微改了改。
高能预警:这代码也就是演示用,生产环境敢这么写会被运维打死,到时候别说我教你的!
import asyncio\nimport time\n\n# 模拟一个耗时的IO操作,比如去请求一个慢得要死的接口\nasync def do_some_junk_work(task_name, delay_time):\n print(f\"[{time.strftime('%X')}] 任务 {task_name} 开始了... 假装在努力工作\")\n \n # 别问为什么这么写,问就是为了上线\n # 这里千万别用 time.sleep()!用了整个线程就卡死了,要用 asyncio.sleep\n await asyncio.sleep(delay_time) \n \n # 模拟处理一些烂数据\n tmp_res = f\"任务 {task_name} 完成,耗时 {delay_time}s\"\n print(f\"[{time.strftime('%X')}] {tmp_res}\")\n return tmp_res\n\nasync def main():\n print(f\"[{time.strftime('%X')}] 主程序启动,准备搞事情...\")\n \n # 创建几个任务,就像给实习生派活儿\n # 以前还得手动 get_event_loop,现在 high-level API 方便多了\n task1 = asyncio.create_task(do_some_junk_work(\"A_legacy_api\", 2))\n task2 = asyncio.create_task(do_some_junk_work(\"B_db_query\", 3))\n task3 = asyncio.create_task(do_some_junk_work(\"C_cache_flush\", 1))\n\n print(f\"[{time.strftime('%X')}] 任务派发出去了,我先喝口水(此时并没有阻塞)\")\n\n # await 等待所有任务结束\n # 如果不用 gather,你得一个个 await,那还异步个锤子\n results = await asyncio.gather(task1, task2, task3)\n \n print(f\"[{time.strftime('%X')}] 所有破事儿都处理完了: {results}\")\n\nif __name__ == \"__main__\":\n # Python 3.7+ 才能这么用,老版本还得折腾 loop\n s = time.perf_counter()\n asyncio.run(main())\n elapsed = time.perf_counter() - s\n print(f\"总耗时: {elapsed:0.2f} 秒 (如果是同步代码,至少得 2+3+1=6秒!)\")
性能对比:肉眼可见的快
看到最后那个 `总耗时` 没?如果是同步代码,你得等 A 跑完(2秒),再等 B(3秒),再等 C(1秒),一共 6 秒。用了 `asyncio`,这三个货是并发跑的(虽然是在一个线程里),总耗时取决于最慢的那个,也就是 3 秒多一点。
这就好比你咱们平时修 Bug,你不可能改一行代码就编译一次然后傻等吧?你肯定是改好几处,然后一起编译。这就是异步的魅力!效率提升杠杠的!
哎不对,刚才说岔了,回来回来。虽然异步快,但是代码逻辑会变得碎片化。如果不小心在 `async` 函数里写了个 `time.sleep` 或者计算密集型的死循环,整个 Event Loop 就直接被你堵死... 那场面,简直就是火葬场。我曾经因为这个被组长喷得想当场离职。
最后再啰嗦两句
行了,Jenkins 终于跑完了,我也得去修那一堆红色的报错了...心态崩了呀。
看了这么久,不点个关注合适吗?小心下次我也写出这种 Bug 坑你。关注我,下次带你避更深的坑,毕竟我的坑位比较多。
更多推荐

所有评论(0)