7Fluent Python(第2版)

并发

并发编程

多线程多进程asyncioGIL

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:并发

一、导读

1.1 本章学习目标

第7章讲解 Python 的并发编程:多线程、多进程、asyncio。

本章核心学习目标:

  • 理解 GIL:为什么 Python 多线程不能真正并行?
  • 掌握多线程:threading 模块
  • 掌握多进程:multiprocessing 模块
  • 掌握 asyncio:异步 I/O 编程

1.2 为什么本章如此重要

并发是现代编程的核心挑战。Python 提供了多种并发模型,理解它们的适用场景至关重要。

1.3 核心问题引导

GIL 是什么?如何绕过它?

什么时候用多线程,什么时候用多进程?

asyncio 的 event loop 如何工作?


二、核心概念详解

2.1 GIL(全局解释器锁)

python
# GIL: 同一时刻只有一个线程执行 Python 字节码
# 影响: CPU 密集型任务无法利用多核
# 不影响: I/O 密集型任务(I/O 时释放 GIL)

import threading
import time

def cpu_bound(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

# 单线程
start = time.perf_counter()
cpu_bound(10_000_000)
cpu_bound(10_000_000)
print(f"Sequential: {time.perf_counter() - start:.2f}s")

# 多线程 (受 GIL 限制,不会更快)
start = time.perf_counter()
t1 = threading.Thread(target=cpu_bound, args=(10_000_000,))
t2 = threading.Thread(target=cpu_bound, args=(10_000_000,))
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Threads: {time.perf_counter() - start:.2f}s")

# 多进程 (可以利用多核)
import multiprocessing
start = time.perf_counter()
p1 = multiprocessing.Process(target=cpu_bound, args=(10_000_000,))
p2 = multiprocessing.Process(target=cpu_bound, args=(10_000_000,))
p1.start(); p2.start()
p1.join(); p2.join()
print(f"Processes: {time.perf_counter() - start:.2f}s")

2.2 asyncio

python
import asyncio

async def fetch_data(url):
    print(f"Fetching {url}")
    await asyncio.sleep(1)  # 模拟 I/O
    return {"url": url, "data": "response"}

async def main():
    # 并发执行多个异步任务
    tasks = [
        fetch_data("http://api1.com"),
        fetch_data("http://api2.com"),
        fetch_data("http://api3.com"),
    ]
    results = await asyncio.gather(*tasks)
    for r in results:
        print(r)

asyncio.run(main())
# 三个请求并发执行,总时间约 1 秒(而非 3 秒)

三、实战案例

3.1 案例一:异步 HTTP 客户端

python
import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/1",
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        for i, r in enumerate(results):
            print(f"URL {i}: {len(r)} bytes")

asyncio.run(main())
# 总时间约 2 秒(最慢的请求),而非 4 秒

四、本章小结

GIL 限制 Python 多线程的 CPU 并行能力

多线程适合 I/O 密集型,多进程适合 CPU 密集型

asyncio 提供高效的异步 I/O 编程


五、思考与练习

如何绕过 GIL 的限制?

asyncio 和 threading 的选择标准是什么?


7/8并发
0%