第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 的选择标准是什么?