Python单线程如何实现万级并发?
晚上加班吃完夜宵之后路过公司天台,有人突然问我:你说Python不是单线程嘛,它咋搞万级并发?你别说还真不是第一次有人问了,尤其搞Web的,天天担心自己一不小心就OOM了。
其实这个东西,说白了还是绕不过IO密集这几个字。你看啊,Python的单线程要真能纯靠CPU跑出万级并发,那得多牛逼,现实就是不可能。GIL(全局解释器锁)卡死了,线程切换都是假的,你看多线程CPU密集那效率,还不如一根棍子单撸。所以单线程高并发,全靠你IO都得让系统帮你挂着,别把CPU占死,能让出来让别的任务插进来跑,这才是关键。
场景唠一唠
上次我们组做个小服务,用flask开了个Web API,白天还行,到晚上爬虫一跑,访问量一下子上去,平时几百个并发突然冲上来几万个请求。你让传统的threading试试,不用一会内存直接炸掉。
那咋办?说白了现在主流方案其实就两种:
协程(asyncio/协程库,比如gevent、eventlet) IO多路复用(select、epoll,当然Python封装后用asyncio和aiohttp一套都搞定了)
你看过eventlet和gevent没?
咱举个最土的例子,比如gevent写个web server,只要不是死循环或者纯CPU计算,基本都能顶一波。下面这个最简单的模拟:
import gevent
from gevent import monkey; monkey.patch_all()
import socket
defhandle(sock, addr):
whileTrue:
data = sock.recv(1024)
ifnot data:
break
sock.sendall(data)
sock.close()
defserver():
s = socket.socket()
s.bind(('0.0.0.0', 8888))
s.listen(5000)
whileTrue:
sock, addr = s.accept()
gevent.spawn(handle, sock, addr)
server()
你别看这个代码就几行,要是IO阻塞(比如网络请求、文件读写)比较多,这种协程切换基本能搞定万级并发。反正我们自己用wrk压到七八千QPS没啥大问题,瓶颈不是代码,是服务器带宽和内存。
asyncio你不熟的话,强烈建议体验下
Python3.5+直接支持async/await,就这俩词写代码,跟写同步一样顺畅,根本不想回头。比如下面这段爬虫代码,原来一页一页爬,爬一千个页面得等到花儿都谢了,换成asyncio+aiohttp分分钟解决:
import asyncio
import aiohttp
asyncdeffetch(session, url):
asyncwith session.get(url) as resp:
returnawait resp.text()
asyncdefmain():
asyncwith aiohttp.ClientSession() as session:
tasks = [fetch(session, f'http://example.com/{i}') for i in range(10000)]
results = await asyncio.gather(*tasks)
asyncio.run(main())
真不是吹,单线程扛一万请求,内存也没怎么涨,CPU更是凉凉,任务切换都是内核和Python自己帮你处理了。
真想用同步风格,那就uvicorn+FastAPI+asyncio
现在流行FastAPI不是没道理,全部异步,写着跟同步一样,看着清爽,实际背后asyncio顶着。你只要别做死循环和长时间CPU运算,基本随便压几千并发都不是事。
就是,不要以为Python单线程搞不定高并发,你只要用对方式——协程、IO多路复用这些新花样,尤其适合那种IO密集场景,Web服务、爬虫、消息队列啥的随便搞。CPU密集还是别指望Python了,乖乖用C/C++/Go/Java吧。
-END-
我为大家打造了一份RPA教程,完全免费:https://www.songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领