You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何asyncio任务切换远慢于threading.Thread?附测试验证

协程真的毫无意义吗?——聊聊你的测试场景与协程的真实价值

嘿,先别着急否定协程的价值~你的测试结果其实是场景错位导致的——你用了一个协程不擅长的纯CPU密集+极端频繁切换场景去和线程对比,自然会得到看似“反常”的结果。咱们一步步拆解:

1. 你的测试代码踩了哪些坑?

先看两个测试的核心逻辑:

  • 线程测试:每次累加都加锁/释放锁,看起来操作频繁,但CPython的GIL(全局解释器锁)限制了同一时刻只有一个线程执行Python字节码,锁的争抢反而可能减少了线程的实际切换次数(大部分时间线程在等锁,而非被内核调度切换)。而且线程数量少的时候,操作系统的调度优化(比如短任务优先)会让切换效率看起来很高。
  • 协程测试:你给每个累加操作都加了await switch(),相当于主动触发协程切换——这是极端频繁的用户态切换,虽然协程切换开销比线程小,但架不住每秒切换几百万次的量级!更关键的是,这个场景是纯CPU计算,完全没有IO等待,协程根本没机会发挥它的核心优势。

2. 协程到底是用来干嘛的?

协程的设计初衷,是解决IO密集型高并发场景的痛点:比如Web服务器处理上千个请求、爬虫爬取几百个页面、数据库批量查询——这些场景里,任务的大部分时间都在等待IO(网络响应、磁盘读写),而非CPU计算。

当一个协程遇到IO等待时,它会主动把执行权让给其他协程,这个切换是用户态的,不需要操作系统内核参与,开销只有线程切换的几十分之一甚至几百分之一。同时,协程的内存占用极低(每个协程栈只有几KB),你可以轻松创建上万个协程,而线程创建几百个就会因为内存开销过大导致性能下降。

3. 你的补充数据其实已经印证了协程的优势

你提到当线程/任务数量增加时,线程变慢但协程基本稳定:

  • 线程数量从2涨到512,单次切换时间从438ns涨到2622ns,开销翻了6倍——这是因为操作系统要调度越来越多的线程,内核上下文切换的开销被不断放大。
  • 协程数量从2涨到4096,单次切换时间基本稳定在4000ns左右——这正是用户态调度的优势:不管协程数量多少,切换开销几乎不变,不会因为任务数量增加而急剧上升。

4. 换个IO密集型场景测试试试

如果把测试改成更贴近真实应用的IO密集场景,结果会完全反转。比如下面的代码,模拟100个任务每个执行100次IO等待:

import time
import asyncio
from threading import Thread

def thread_io_test():
    def io_task():
        for _ in range(100):
            time.sleep(0.01)  # 模拟同步IO等待
    threads = [Thread(target=io_task) for _ in range(100)]
    start = time.time()
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(f"Thread IO test finished in {round(time.time()-start,4)}s")

async def async_io_test():
    async def io_task():
        for _ in range(100):
            await asyncio.sleep(0.01)  # 模拟异步IO等待
    tasks = [io_task() for _ in range(100)]
    start = time.time()
    await asyncio.gather(*tasks)
    print(f"AsyncIO IO test finished in {round(time.time()-start,4)}s")

if __name__ == "__main__":
    thread_io_test()
    asyncio.run(async_io_test())

你会发现:线程版本的执行时间大概在10秒左右(线程sleep时会被内核挂起,CPU核心有限的情况下无法真正并行处理所有等待);而协程版本只需要1秒左右——因为所有协程的IO等待是并发的,当一个协程sleep时,其他协程会立刻接过执行权,总时间等于单个任务的等待时间总和。

总结

协程不是用来替代线程做CPU密集型任务的,它的价值在于IO密集型高并发场景

  • 更低的上下文切换开销
  • 极小的内存占用,支持海量并发
  • 更简洁的异步代码编写方式(对比线程的锁、回调地狱)

你的测试场景刚好是协程的“短板”,但这并不代表协程毫无意义——选对场景,它能爆发出远超线程的性能。

内容的提问来源于stack exchange,提问作者AdamHommer

火山引擎 最新活动