首页 / Python3 入门教程 / 进程 vs 线程与异步初探

Python3 入门教程

进程 vs 线程与异步初探

本教程共 70 篇 · 第 57 篇 · 更新于 2026-07-22 · 约 5 分钟阅读

PythonPython3 入门教程进程线程GILconcurrent.futuresasyncio

57. 进程 vs 线程与异步初探

本节目标:搞清楚什么时候用进程、什么时候用线程,理解 GIL 的影响,学会用 concurrent.futures 简化并发代码。

前面两节分别讲了多进程和多线程,但你可能会问:到底选哪个?这节就来掰扯清楚,并引出现代 Python 里更省心的写法。

GIL:Python 线程的隐形天花板

GIL 全称 Global Interpreter Lock,是 CPython 解释器里的一把全局锁。它的存在保证了同一时刻只有一个线程在执行 Python 字节码,避免了解释器内部数据结构的竞争问题。

这把锁带来的直接后果是:纯 Python 的多线程无法利用多核 CPU。你开 8 个线程跑计算密集型任务,实际上只占满 1 个核心,另外 7 个核心在旁边看戏。

import threading, time

def cpu_bound():
    count = 0
    for _ in range(50_000_000):
        count += 1

start = time.time()
t1 = threading.Thread(target=cpu_bound)
t2 = threading.Thread(target=cpu_bound)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"多线程耗时: {time.time() - start:.2f} 秒")

start = time.time()
cpu_bound()
cpu_bound()
print(f"单线程顺序耗时: {time.time() - start:.2f} 秒")

在 CPython 上跑这段代码,你会发现两种方式的耗时差不多,甚至多线程还略慢——因为线程切换本身也有开销。

Note

GIL 只影响 CPython。如果你用 PyPy、Jython 或者其他实现,行为可能不同。另外,线程在执行 C 语言扩展(如 NumPy 的矩阵运算)时,会释放 GIL,这时多线程可以真正并行。

计算密集型 vs IO 密集型

选进程还是选线程,核心看你的任务类型。

计算密集型:大量数学运算、图像处理、视频编码。CPU 一直在干活,很少等待。这类任务用多进程最合适,每个进程有独立的 GIL,可以跑满所有核心。

IO 密集型:网络请求、文件读写、数据库查询。大部分时间花在等网络上,CPU 利用率很低。这类任务用多线程异步 IO 更合适,因为切换代价小,能在等待时去做别的事。

打个比方:计算密集型像厨师一直在切菜,线程切换等于让他放下刀、换围裙、再拿起刀,反而耽误事;IO 密集型像厨师把汤炖上后,空闲时间去炒菜, multitasking 就合理了。

concurrent.futures:更现代的接口

Python 3.2 引入了 concurrent.futures 模块,把进程池和线程池包装成统一的接口。你不再需要手动管理 ProcessThread,代码更清爽。

线程池版:

from concurrent.futures import ThreadPoolExecutor
import time

def fetch(url):
    time.sleep(1)
    return f"{url} 完成"

urls = ['url-1', 'url-2', 'url-3', 'url-4']

with ThreadPoolExecutor(max_workers=4) as executor:
    results = executor.map(fetch, urls)
    for r in results:
        print(r)

进程池版只需把 ThreadPoolExecutor 换成 ProcessPoolExecutor,其他代码几乎不变:

from concurrent.futures import ProcessPoolExecutor

def cpu_task(n):
    return sum(range(n))

numbers = [10_000_000, 20_000_000, 30_000_000]

with ProcessPoolExecutor(max_workers=3) as executor:
    results = executor.map(cpu_task, numbers)
    print(list(results))
Tip

as_completed() 适合「谁先做完先处理谁」的场景,不必等全部任务结束:

from concurrent.futures import ThreadPoolExecutor, as_completed
with ThreadPoolExecutor() as ex:
    futures = {ex.submit(fetch, u): u for u in urls}
    for future in as_completed(futures):
        print(futures[future], future.result())

异步 IO 初探

多线程虽然能并发 IO,但线程数量一多,操作系统调度开销也会上涨。有没有更轻量的方案?

现代操作系统支持异步 IO:程序发起一个网络请求后不必阻塞等待,操作系统会在数据就绪后通知程序。Python 3.4+ 的 asyncio 模块就是基于这个机制,用单线程+事件循环实现高并发。一个线程可以管理成千上万个并发连接,比线程池还省资源。

这里先给你看个雏形,下两节会深入讲:

import asyncio

async def say_hello():
    print("hello")
    await asyncio.sleep(1)
    print("world")

asyncio.run(say_hello())

async def 定义协程,await 挂起当前任务让出控制权。整个程序只有一个线程在跑,但绝不会因为等网络而傻站着。

小结

场景推荐方案理由
计算密集型多进程 / ProcessPoolExecutor绕过 GIL,利用多核
IO 密集型、中等并发多线程 / ThreadPoolExecutor写法简单,切换开销可接受
IO 密集型、高并发asyncio + async/await单线程管理大量连接,资源占用最低
  • GIL 让 CPython 的多线程无法并行执行 Python 代码,但 IO 等待期间会释放 GIL。
  • concurrent.futures 提供了进程池和线程池的统一封装,比底层 API 更好用。
  • 异步 IO 是处理高并发网络请求的趋势,后续章节会详细展开。

来源:参考了 liaoxuefeng「14.4. 进程 vs. 线程」、runoob「Python3 多线程」等,改写后所得。