You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

I/O密集型场景下多线程比多进程慢的原因排查

多进程处理I/O密集任务反而更快的原因分析

你遇到的现象和常规认知有差异,结合你提供的代码,主要可以从以下几个角度解释:

1. 线程池并发数设置过低

你的多线程代码里ThreadPoolExecutor仅设置了max_workers=5,而multiprocessing.Pool默认会使用与CPU核心数一致的进程数(比如常见的8核CPU会启动8个进程)。I/O密集型任务的核心特点是线程/进程大部分时间在等待网络响应,所以并发数可以远高于CPU核心数——5个线程的并发能力远不如多进程的并发数,自然处理速度会慢很多。

2. 打印操作带来的锁竞争

多线程里的print是线程安全操作,底层会加全局锁。当大量线程同时执行print时,会频繁触发锁的获取与释放,产生额外的等待开销。而多进程的每个进程拥有独立的输出流,打印操作不会有跨进程的锁竞争,这部分开销被完全避免了。

3. GIL的隐性影响

虽然I/O操作会释放Python的全局解释器锁(GIL),但requests库的部分底层操作(比如DNS解析、某些C扩展逻辑)可能并没有完全释放GIL,或者GIL的切换过程本身带来了额外开销。多进程完全绕开了GIL的限制,每个进程拥有独立的Python解释器,不会受GIL约束。

4. 会话复用的细节差异

多线程用thread_local给每个线程分配一个Session,多进程则是每个进程一个Session。两者都实现了连接复用,但进程级的Session在某些网络场景下(比如服务器端的连接池策略)可能表现更优,不过这一点的影响相对前几点要小。


验证建议

  • 把多线程的max_workers调整到30或50,再对比测试结果;
  • 注释掉两个版本中的print语句,消除打印带来的开销后重新运行;
  • 用multiprocessing.cpu_count()查看多进程Pool的默认大小,确认并发数的差异。

你的测试代码

多进程代码

import requests
import multiprocessing
import time

session = None


def set_global_session():
    global session
    if not session:
        session = requests.Session()


def download_site(url):
    with session.get(url) as response:
        name = multiprocessing.current_process().name
        print(f"{name}:Read {len(response.content)} from {url}")


def download_all_sites(sites):
    with multiprocessing.Pool(initializer=set_global_session) as pool:
        pool.map(download_site, sites)


if __name__ == "__main__":
    sites = [
        "https://www.jython.org",
        "http://olympus.realpython.org/dice",
    ] * 300
    start_time = time.time()
    download_all_sites(sites)
    duration = time.time() - start_time
    print(f"Downloaded {len(sites)} in {duration} seconds")

多线程代码

import concurrent.futures
import requests
import threading
import time


thread_local = threading.local()


def get_session():
    if not hasattr(thread_local, "session"):
        thread_local.session = requests.Session()
    return thread_local.session


def download_site(url):
    session = get_session()
    with session.get(url) as response:
        print(f"Read {len(response.content)} from {url}")


def download_all_sites(sites):
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        executor.map(download_site, sites)


if __name__ == "__main__":
    sites = [
        "https://www.jython.org",
        "http://olympus.realpython.org/dice",
    ] * 300
    start_time = time.time()
    download_all_sites(sites)
    duration = time.time() - start_time
    print(f"Downloaded {len(sites)} in {duration} seconds")

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:21:15