分享
  1. 首页
  2. 文章

Python 异步 IO(asyncio)、多进程、多线程性能对比

· · 2036 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

IO 密集型应用
IO 密集型应用CPU等待IO时间远大于CPU 自身运行时间,太浪费;常见的 IO 密集型业务包括:浏览器交互、磁盘请求、网络爬虫、数据库请求等

image.png

Python 世界对于 IO 密集型场景的并发提升有 3 种方法:多进程、多线程、异步 IO(asyncio);理论上讲asyncio是性能最高的,原因如下:
1.进程、线程会有CPU上下文切换
2.进程、线程需要内核态和用户态的交互,性能开销大;而协程对内核透明的,只在用户态运行
3.进程、线程并不可以无限创建,最佳实践一般是 CPU*2;而协程并发能力强,并发上限理论上取决于操作系统IO多路复用(Linux下是 epoll)可注册的文件描述符的极限

那asyncio的实际表现是否如理论上那么强,到底强多少呢?我构建了如下测试场景:

访问500台 DB,并sleep 100ms模拟业务查询 
方法 1;顺序串行一台台执行
方法 2:多进程
方法 3:多线程
方法 4:asyncio
方法 5:asyncio+uvloop

最后的asyncio+uvloop和官方asyncio 最大不同是用 Cython+libuv 重新实现了asyncio 的事件循环(event loop)部分,官方测试性能是 node.js的 2 倍,持平 golang。

以下测试代码需要 Pyhton3.7+:
顺序串行一台台执行

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import records
user=xx
pass=xx
port=xx
hosts= [....] # 500台 db列表
def query(host):
 conn = records.Database(
 f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
 rows = conn.query('select sleep(0.1);')
 print(rows[0])
def main():
 for h in hosts:
 query(h)
# main entrance
if __name__ == '__main__':
 main()

多进程

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concurrent import futures
import records
user=xx
pass=xx
port=xx
hosts= [....] # 500台 db列表
def query(host):
 conn = records.Database(
 f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
 rows = conn.query('select sleep(0.1);')
 print(rows[0])
def main():
 with futures.ProcessPoolExecutor() as executor:
 for future in executor.map(query,hosts):
 pass
# main entrance
if __name__ == '__main__':
 main()

多线程

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concurrent import futures
import records
user=xx
pass=xx
port=xx
hosts= [....] # 500台 db列表
def query(host):
 conn = records.Database(
 f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
 rows = conn.query('select sleep(0.1);')
 print(rows[0])
def main():
 with futures.ThreadPoolExecutor() as executor:
 for future in executor.map(query,hosts):
 pass
# main entrance
if __name__ == '__main__':
 main()

asyncio

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
from databases import Database 
user=xx
pass=xx
port=xx
hosts= [....] # 500台 db列表
async def query(host):
 DATABASE_URL = f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4'
 async with Database(DATABASE_URL) as database:
 query = 'select sleep(0.1);'
 rows = await database.fetch_all(query=query)
 print(rows[0])
async def main():
 tasks = [asyncio.create_task(query(host)) for host in hosts]
 await asyncio.gather(*tasks)
# main entrance
if __name__ == '__main__':
 asyncio.run(main())

asyncio+uvloop

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import uvloop
from databases import Database 
user=xx
pass=xx
port=xx
hosts= [....] # 500台 db列表
async def query(host):
 DATABASE_URL = f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4'
 async with Database(DATABASE_URL) as database:
 query = 'select sleep(0.1);'
 rows = await database.fetch_all(query=query)
 print(rows[0])
async def main():
 tasks = [asyncio.create_task(query(host)) for host in hosts]
 await asyncio.gather(*tasks)
# main entrance
if __name__ == '__main__':
 uvloop.install()
 asyncio.run(main())

运行时间对比

方式 运行时间
串行 1m7.745s
多进程 2.932s
多线程 4.813s
asyncio 1.068s
asyncio+uvloop 0.750s

可以看出: 无论多进程、多进程还是asyncio都能大幅提升IO 密集型场景下的并发,但asyncio+uvloop性能最高,运行时间只有原始串行运行时间的 1/90,相差快 2 个数量级了!

内存占用对比
串行

image.png

多进程
image.png

多线程
image.png

asyncio

image.png

asyncio+uvloop

image.png

可以看出asyncio+uvloop内存占用表现仍然最优,只有 60M;而多进程占用多达 1.4G,果然创建进程是个十分重的操作~

总结
asyncio 无论运行时间还是内存占用都远优于多进程、多线程,配合 uvloop 性能还能进一步提升,在 IO 密集型业务中可以优先使用 asyncio。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2036 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏