Python技术迷

Python单线程如何实现万级并发?

哎,前几天加班到一点多,还在公司楼下点外卖那会儿,有个哥们突然在群里问,说Python到底能不能搞那种,单线程就支持一两万并发的那种效果——说实话,这问题问出来我当时都快笑了,心想兄弟你是刚写python还是怎么着,哈哈,不过别说,这种需求其实挺多小公司和个人项目经常会碰到。

其实咱们说实话,Python的GIL(全局解释器锁)真的是…算了不吐槽了,反正要靠多线程搞万级并发,纯属想多了。正常写threading,线程数到几百就卡死你没商量。你看人家Go、Java那种,线程本身就是轻量级的,协程啥的天生高并发。但Python这块…要不是有点奇技淫巧,早就被喷成渣渣。

但吧,你别说,现在真要写单线程支持上万并发,还真有门路——靠的就是异步IO,asyncio和select那些玩意儿。前几天晚上回家地铁上刷手机,还看到个老哥说自己用asyncio爬虫,一台破笔记本居然开了一万多个请求还不挂。其实这个思路和Nginx、Node.js差不多——单线程事件循环,IO全都非阻塞,CPU基本都用在处理回调那点儿业务逻辑上。

举个场景,像那种微信小程序后端、刷接口、或者是大批量爬取网页的需求,根本不需要CPU计算,就纯靠IO,你用asyncio、aiohttp这些,开个一万任务分分钟的事。

随便扔个代码片段,别太认真哈,回头有bug别喷我,我就是临时想起来写的:

import asyncio
import aiohttp

asyncdeffetch(url):
asyncwith aiohttp.ClientSession() as session:
asyncwith session.get(url) as resp:
# 有时候这里要加个await resp.text()啥的
# 懒得写了,直接返回
returnawait resp.text()

asyncdefmain():
    tasks = []
for i in range(10000):
        tasks.append(fetch(f'http://example.com/?id={i}'))
# 等等,手滑打错了,这里要用gather
    results = await asyncio.gather(*tasks)
    print(len(results))

# 那啥,Py3.7以后直接这样
if __name__ == '__main__':
    asyncio.run(main())

你看啊,这东西它牛就牛在,你开的不是一万个线程,后台全是事件循环在调度,内存CPU吃的很少。只要你网卡别炸,Python的asyncio其实还挺顶用的。

不过有一点,我和我们组小李经常吐槽:你别想着每个请求都算复杂逻辑啥的,真要CPU跑满,还是得用多进程或者分布式。这套asyncio纯粹就是为了IO密集——比如爬虫、接口转发、聊天IM那种。你要是写个深度学习模型,别瞎折腾了,还不如丢到多进程Pool里。

而且啊,有些老项目,写的全是阻塞IO,想改成asyncio还挺费劲。你得保证所有的库都支持async,要不就卡那一步,等死。

那天晚上搞定一堆接口测试脚本,回家已经快十二点了,电脑都快没电了。还有人问:为啥不用gevent?其实思路差不多,也是协程、事件循环,不过现在主流都用asyncio了,语法还更官方。

总结下吧——其实万级并发真不是Python的短板,就看你场景合不合适。像asyncio、aiohttp这种,一顿操作猛如虎,写着贼爽。但真到CPU密集、数据库瓶颈啥的,再多的并发也没用。最后一句,兄弟们真要用,提前测好你网速、系统文件句柄限制那些,不然1000个连接刚一跑,直接报错爆栈那种,别问我怎么知道的,都是血泪史…

-END-

我为大家打造了一份RPA教程,完全免费:https://www.songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领