学Python异步编程这件事,我前前后后折腾了不下五次。每次翻开教程看到“事件循环”“协程”“await”这几个词,我就有种想关掉浏览器的冲动。但工作需要,我不得不硬着头皮上。
第一次接触是看别人写的爬虫代码。那代码里全是async def和await,我一头雾水。我试着复制下来运行,结果直接报错。那时候我甚至不知道要装aiohttp,更不知道要创建一个事件循环。我看着屏幕上红色的报错信息,心想这东西跟我无缘。
后来有个活要抓几千个网页。用普通的requests库跑,一个页面等好几秒,总共要跑将近一个小时。同事说他用异步方式十几分钟就搞定。我羡慕得不行,又翻开教程从头看。这次我总算弄明白了一件事,异步不是让代码跑得更快,而是让枯燥的等待时间能被利用起来做别的事。比如等网页响应的时候,CPU可以先去处理别的任务。这个认知让我觉得异步没那么神秘了。
真正开始动手的时候,我又栽了跟头。我照猫画虎写了个异步爬虫,结果发现有些请求总是超时。检查了半天,发现是我在协程里用了同步的time.sleep。这个函数会阻塞整个事件循环,等于是把异步变成了同步,完全白干了。换成await asyncio.sleep才解决问题。这个坑我掉了三次才记住。
还有一次让我打击很大。我写了个异步文件读取的程序,想着应该很快。结果跑起来比同步还慢。查资料才知道,文件IO操作在Python里用异步并不会提速,因为底层用的是系统调用。异步主要对网络IO这些需要等待的操作有效。这种概念上的误会让我浪费了好几天。
让我彻底学会的那次,是因为我放下了想一步到位的念头。我找了个非常简单的需求,用异步写一个定时任务。没有网络请求,没有复杂的资源调度,就单纯的每隔几秒打印一句话。学会异步不是靠懂多少理论,而是能写出一个能跑的最小程序。那个程序只有二十行代码,但我看着它正确输出结果的时候,觉得之前的努力都没白费。
现在回头想,异步编程就是处理等待的艺术。你不需要记住所有概念,只需要知道什么时候该用await,什么时候不该用。我学会了以后反而觉得这东西其实不复杂,复杂的是我当初那颗想走捷径的心。每个技术都有它的使用场景,强用乱用只会让自己更痛苦。