目录
第一部分:基础概念篇
- 一、核心概念定义
- 1.1 并发(Concurrency)
- 1.2 并行(Parallelism)
- 1.3 进程(Process)
- 1.4 线程(Thread)
- 1.5 协程(Coroutine)
- 1.6 CPU密集型任务
- 1.7 IO密集型任务
第二部分:对比分析篇
第三部分:底层原理篇
- 五、内部实现原理深度剖析
- 5.1 多进程内部实现原理
- 5.2 多线程内部实现原理
- 5.3 协程内部实现原理
- 六、为什么能实现并发与并行?
- 6.1 问题的本质
- 6.2 进程实现原理
- 6.3 线程实现原理
- 6.4 协程实现原理
- 6.5 三者对比总结
- 6.6 实战验证
第四部分:实战应用篇
一、核心概念定义
1.1 并发(Concurrency)
定义: 并发是指系统能够处理多个任务的能力,这些任务在时间上可以交替执行,给人一种"同时"执行的感觉。
本质: 一个CPU在多个任务之间快速切换
类比:
- 一个厨师在做三道菜:炒菜A、煮汤B、蒸饭C
- 厨师先炒菜A一会儿,然后去看汤B,再去检查饭C
- 虽然同一时刻只做一件事,但通过快速切换,三道菜都在"同时"进行
import time
import threading
def task(name, duration):
"""模拟并发任务"""
print(f"[{time.strftime('%H:%M:%S')}] {name} 开始")
time.sleep(duration)
print(f"[{time.strftime('%H:%M:%S')}] {name} 完成")
# 并发执行
threads = []
for i in range(3):
t = threading.Thread(target=task, args=(f"任务{i+1}", 2))
threads.append(t)
t.start()
for t in threads:
t.join()
# 输出示例(交替执行):
# [14:30:00] 任务1 开始
# [14:30:00] 任务2 开始
# [14:30:00] 任务3 开始
# [14:30:02] 任务1 完成
# [14:30:02] 任务2 完成
# [14:30:02] 任务3 完成
关键特征:
- ✅ 任务可以交替执行
- ✅ 不需要多核CPU
- ✅ 适合I/O密集型任务
- ❌ 同一时刻只有一个任务真正在执行
1.2 并行(Parallelism)
定义: 并行是指多个任务在同一时刻真正同时执行,需要多核CPU或多个处理器。
本质: 多个CPU同时执行多个任务
类比:
- 三个厨师同时做三道菜
- 每个厨师专注于自己的菜,真正的同时进行
- 效率是单个厨师的三倍
import time
from multiprocessing import Process, cpu_count
def cpu_task(name, n):
"""CPU密集型任务"""
print(f"[{time.strftime('%H:%M:%S')}] {name} 开始,进程ID: {os.getpid()}")
result = 0
for i in range(n):
result += i ** 2
print(f"[{time.strftime('%H:%M:%S')}] {name} 完成")
return result
if __name__ == '__main__':
print(f"CPU核心数: {cpu_count()}")
# 并行执行
processes = []
for i in range(4):
p = Process(target=cpu_task, args=(f"任务{i+1}", 10000000))
processes.append(p)
p.start()
for p in processes:
p.join()
# 输出示例(真正同时执行,如果有4核CPU):
# CPU核心数: 4
# [14:30:00] 任务1 开始,进程ID: 1234
# [14:30:00] 任务2 开始,进程ID: 1235
# [14:30:00] 任务3 开始,进程ID: 1236
# [14:30:00] 任务4 开始,进程ID: 1237
# [14:30:02] 任务1 完成
# [14:30:02] 任务2 完成
# [14:30:02] 任务3 完成
# [14:30:02] 任务4 完成
关键特征:
- ✅ 任务真正同时执行
- ✅ 需要多核CPU
- ✅ 适合CPU密集型任务
- ✅ 性能提升明显
1.3 进程(Process)
定义: 进程是操作系统进行资源分配和调度的基本单位,拥有独立的内存空间。
特点:
- 独立的内存空间
- 进程间通信需要特殊机制(IPC)
- 创建和销毁开销大
- 安全隔离性好
- 不受GIL限制
from multiprocessing import Process, Queue, Pipe
import os
import time
# 场景1:独立计算任务
def calculate_square(numbers, result_queue):
"""计算平方数"""
pid = os.getpid()
squares = [n**2 for n in numbers]
result_queue.put((pid, squares))
print(f"进程 {pid} 完成计算")
if __name__ == '__main__':
# 使用队列在进程间传递数据
result_queue = Queue()
processes = []
data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for chunk in data:
p = Process(target=calculate_square, args=(chunk, result_queue))
processes.append(p)
p.start()
for p in processes:
p.join()
# 获取所有结果
results = []
while not result_queue.empty():
results.append(result_queue.get())
print(f"所有结果: {results}")
# 场景2:进程间通信(Pipe)
def sender(conn):
"""发送数据的进程"""
messages = ['消息1', '消息2', '消息3']
for msg in messages:
conn.send(msg)
print(f"发送: {msg}")
time.sleep(0.5)
conn.send('DONE')
conn.close()
def receiver(conn):
"""接收数据的进程"""
while True:
msg = conn.recv()
if msg == 'DONE':
break
print(f"接收: {msg}")
conn.close()
if __name__ == '__main__':
# 创建管道
parent_conn, child_conn = Pipe()
# 创建进程
p1 = Process(target=sender, args=(parent_conn,))
p2 = Process(target=receiver, args=(child_conn,))
p1.start()
p2.start()
p1.join()
p2.join()
使用场景:
-
CPU密集型计算
- 科学计算
- 图像/视频处理
- 数据分析
- 机器学习训练
-
需要隔离的任务
- 沙箱执行
- 第三方代码运行
- 需要独立崩溃恢复
-
多核利用
- 批量数据处理
- 并行渲染
- 分布式计算
1.4 线程(Thread)
定义: 线程是进程内的执行单元,共享进程的内存空间。
特点:
- 共享内存空间
- 创建开销小
- 通信简单直接
- 受GIL限制(CPython)
- 需要注意线程安全
import threading
import time
from queue import Queue
# 场景1:生产者-消费者模式
class ProducerConsumer:
def __init__(self):
self.queue = Queue(maxsize=10)
self.lock = threading.Lock()
self.total_produced = 0
self.total_consumed = 0
def producer(self, name, num_items):
"""生产者线程"""
for i in range(num_items):
item = f"{name}-item-{i}"
self.queue.put(item)
with self.lock:
self.total_produced += 1
print(f"[生产者 {name}] 生产: {item}, 队列大小: {self.queue.qsize()}")
time.sleep(0.1)
def consumer(self, name):
"""消费者线程"""
while True:
try:
item = self.queue.get(timeout=1)
with self.lock:
self.total_consumed += 1
print(f"[消费者 {name}] 消费: {item}, 队列大小: {self.queue.qsize()}")
time.sleep(0.2)
self.queue.task_done()
except:
break
# 运行示例
pc = ProducerConsumer()
# 创建生产者线程
producers = [
threading.Thread(target=pc.producer, args=(f"P{i}", 5))
for i in range(2)
]
# 创建消费者线程
consumers = [
threading.Thread(target=pc.consumer, args=(f"C{i}",), daemon=True)
for i in range(3)
]
# 启动所有线程
for p in producers:
p.start()
for c in consumers:
c.start()
# 等待生产者完成
for p in producers:
p.join()
# 等待队列清空
pc.queue.join()
print(f"\n生产总数: {pc.total_produced}")
print(f"消费总数: {pc.total_consumed}")
# 场景2:线程池
from concurrent.futures import ThreadPoolExecutor
import requests
def download_url(url):
"""下载网页"""
print(f"开始下载: {url}")
response = requests.get(url, timeout=5)
print(f"完成下载: {url}, 大小: {len(response.content)} bytes")
return url, len(response.content)
urls = [
'https://www.python.org',
'https://www.github.com',
'https://www.stackoverflow.com',
]
# 使用线程池
with ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(download_url, urls)
for url, size in results:
print(f"{url}: {size} bytes")
使用场景:
-
I/O密集型任务
- 网络请求(爬虫、API调用)
- 文件读写
- 数据库查询
-
GUI应用
- 保持界面响应
- 后台任务处理
-
并发服务器
- Web服务器
- Socket服务器
1.5 协程(Coroutine)
定义: 协程是一种用户态的轻量级线程,由程序自己控制调度,不需要线程上下文切换。
特点:
- 单线程内执行
- 极低的切换开销
- 不需要锁机制
- 基于事件循环
- 使用async/await语法
import asyncio
import aiohttp
import time
# 场景1:异步HTTP请求
async def fetch_url(session, url):
"""异步获取URL"""
print(f"[{time.strftime('%H:%M:%S')}] 开始请求: {url}")
async with session.get(url) as response:
data = await response.text()
print(f"[{time.strftime('%H:%M:%S')}] 完成请求: {url}, 大小: {len(data)}")
return url, len(data)
async def fetch_all_urls(urls):
"""并发获取多个URL"""
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# 运行
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/3',
]
start = time.time()
results = asyncio.run(fetch_all_urls(urls))
elapsed = time.time() - start
print(f"\n总耗时: {elapsed:.2f}秒")
print(f"结果: {results}")
# 场景2:异步文件I/O
import aiofiles
async def read_file_async(filepath):
"""异步读取文件"""
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
content = await f.read()
return len(content)
async def process_files(filepaths):
"""并发处理多个文件"""
tasks = [read_file_async(fp) for fp in filepaths]
sizes = await asyncio.gather(*tasks)
return sizes
# 场景3:异步生产者-消费者
async def producer(queue, name, num_items):
"""异步生产者"""
for i in range(num_items):
item = f"{name}-{i}"
await queue.put(item)
print(f"生产: {item}")
await asyncio.sleep(0.1)
async def consumer(queue, name):
"""异步消费者"""
while True:
item = await queue.get()
print(f"[消费者 {name}] 消费: {item}")
await asyncio.sleep(0.2)
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=10)
# 创建生产者和消费者任务
producers = [
asyncio.create_task(producer(queue, f"P{i}", 5))
for i in range(2)
]
consumers = [
asyncio.create_task(consumer(queue, f"C{i}"))
for i in range(3)
]
# 等待生产者完成
await asyncio.gather(*producers)
# 等待队列清空
await queue.join()
# 取消消费者
for c in consumers:
c.cancel()
# asyncio.run(main())
# 场景4:异步数据库操作
import asyncpg
async def fetch_users_async():
"""异步查询数据库"""
conn = await asyncpg.connect(
user='user', password='password',
database='database', host='127.0.0.1'
)
# 并发执行多个查询
queries = [
conn.fetch('SELECT * FROM users WHERE age > $1', 18),
conn.fetch('SELECT * FROM orders WHERE status = $1', 'pending'),
conn.fetch('SELECT * FROM products WHERE price < $1', 100),
]
results = await asyncio.gather(*queries)
await conn.close()
return results
使用场景:
-
高并发I/O操作
- 异步Web框架(FastAPI、aiohttp)
- 爬虫(成千上万个并发请求)
- WebSocket服务器
-
异步数据库操作
- 大量数据库查询
- 实时数据处理
-
微服务通信
- 异步RPC调用
- 消息队列处理
二、并发 vs 并行
对比表格
| 特性 | 并发 | 并行 |
|---|---|---|
| 执行方式 | 交替执行 | 同时执行 |
| CPU需求 | 单核可以 | 需要多核 |
| 资源利用 | 时间片轮转 | 真正的多核利用 |
| 适用场景 | I/O密集型 | CPU密集型 |
| 实现方式 | 线程、协程 | 多进程 |
| 性能提升 | I/O等待时间 | 计算速度 |
图示对比
并发(Concurrency):
时间 →
CPU: [任务A][任务B][任务A][任务C][任务B][任务A]...
↑ 快速切换,看起来同时进行
并行(Parallelism):
时间 →
CPU1: [任务A][任务A][任务A][任务A]...
CPU2: [任务B][任务B][任务B][任务B]...
CPU3: [任务C][任务C][任务C][任务C]...
↑ 真正同时执行
实战对比示例
import time
import threading
from multiprocessing import Process
import asyncio
def cpu_bound_task(n):
"""CPU密集型任务:计算"""
return sum(i * i for i in range(n))
def io_bound_task():
"""I/O密集型任务:模拟网络请求"""
time.sleep(1)
return "完成"
# 测试1:CPU密集型 - 串行 vs 并发 vs 并行
def test_cpu_intensive():
n = 10000000
# 串行
start = time.time()
for _ in range(4):
cpu_bound_task(n)
serial_time = time.time() - start
# 并发(线程)- GIL限制,性能差
start = time.time()
threads = [threading.Thread(target=cpu_bound_task, args=(n,)) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
concurrent_time = time.time() - start
# 并行(进程)- 真正的并行
start = time.time()
processes = [Process(target=cpu_bound_task, args=(n,)) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
parallel_time = time.time() - start
print("CPU密集型任务对比:")
print(f"串行: {serial_time:.2f}秒")
print(f"并发(线程): {concurrent_time:.2f}秒")
print(f"并行(进程): {parallel_time:.2f}秒")
print(f"并行提速: {serial_time/parallel_time:.2f}x\n")
# 测试2:I/O密集型 - 串行 vs 并发
def test_io_intensive():
# 串行
start = time.time()
for _ in range(10):
io_bound_task()
serial_time = time.time() - start
# 并发(线程)
start = time.time()
threads = [threading.Thread(target=io_bound_task) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
concurrent_time = time.time() - start
# 协程
async def async_io_task():
await asyncio.sleep(1)
return "完成"
async def test_coroutine():
tasks = [async_io_task() for _ in range(10)]
await asyncio.gather(*tasks)
start = time.time()
asyncio.run(test_coroutine())
coroutine_time = time.time() - start
print("I/O密集型任务对比:")
print(f"串行: {serial_time:.2f}秒")
print(f"并发(线程): {concurrent_time:.2f}秒")
print(f"协程: {coroutine_time:.2f}秒")
print(f"并发提速: {serial_time/concurrent_time:.2f}x")
print(f"协程提速: {serial_time/coroutine_time:.2f}x")
# 运行测试
if __name__ == '__main__':
test_cpu_intensive()
test_io_intensive()
三、进程 vs 线程 vs 协程
详细对比表
| 特性 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 调度者 | 操作系统 | 操作系统 | 程序自己 |
| 切换开销 | 大(ms级) | 中(μs级) | 小(ns级) |
| 内存 | 独立 | 共享 | 共享 |
| 通信 | IPC(复杂) | 共享内存(简单) | 函数调用(最简单) |
| 数据安全 | 完全隔离 | 需要锁 | 不需要锁 |
| 创建数量 | 几十个 | 几百个 | 几万个 |
| GIL影响 | 无 | 有 | 无(单线程) |
| CPU利用 | 多核并行 | 受GIL限制 | 单核并发 |
| 适用场景 | CPU密集型 | I/O密集型 | 高并发I/O |
内存模型对比
import os
import threading
import asyncio
from multiprocessing import Process, Value
# 全局变量
global_counter = 0
# 进程示例:独立内存空间
def process_increment(shared_value):
"""进程:修改共享内存需要特殊机制"""
for _ in range(100000):
shared_value.value += 1
print(f"进程 {os.getpid()} 完成,值: {shared_value.value}")
def test_process_memory():
# 使用Value创建共享内存
shared_counter = Value('i', 0)
processes = [
Process(target=process_increment, args=(shared_counter,))
for _ in range(4)
]
for p in processes:
p.start()
for p in processes:
p.join()
print(f"进程最终值: {shared_counter.value}")
# 线程示例:共享内存空间
def thread_increment():
"""线程:直接访问全局变量,需要锁保护"""
global global_counter
for _ in range(100000):
global_counter += 1 # 不安全!
def thread_increment_safe(lock):
"""线程:使用锁保护"""
global global_counter
for _ in range(100000):
with lock:
global_counter += 1
def test_thread_memory():
global global_counter
# 不安全的版本
global_counter = 0
threads = [threading.Thread(target=thread_increment) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"线程不安全版本: {global_counter} (应该是400000)")
# 安全的版本
global_counter = 0
lock = threading.Lock()
threads = [threading.Thread(target=thread_increment_safe, args=(lock,)) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"线程安全版本: {global_counter}")
# 协程示例:单线程,不需要锁
async def coroutine_increment(counter):
"""协程:单线程执行,天然线程安全"""
for _ in range(100000):
counter['value'] += 1
async def test_coroutine_memory():
counter = {'value': 0}
tasks = [coroutine_increment(counter) for _ in range(4)]
await asyncio.gather(*tasks)
print(f"协程版本: {counter['value']}")
if __name__ == '__main__':
print("=== 内存模型测试 ===\n")
test_process_memory()
test_thread_memory()
asyncio.run(test_coroutine_memory())
四、CPU密集型 vs IO密集型
CPU密集型任务(CPU-bound)
定义: 主要消耗CPU资源的任务,大部分时间在进行计算。
特征:
- CPU使用率高(接近100%)
- 内存访问频繁
- 很少等待I/O
- 受CPU性能限制
典型场景:
import time
import math
from multiprocessing import Process, Pool
import numpy as np
# 场景1:数学计算
def calculate_pi(n):
"""蒙特卡洛法计算π"""
inside = 0
for _ in range(n):
x = np.random.random()
y = np.random.random()
if x*x + y*y <= 1:
inside += 1
return 4 * inside / n
# 场景2:图像处理
def process_image(image_data):
"""图像处理:滤波、变换等"""
# 高斯模糊
result = np.zeros_like(image_data)
for i in range(1, image_data.shape[0]-1):
for j in range(1, image_data.shape[1]-1):
# 3x3卷积核
result[i,j] = np.sum(image_data[i-1:i+2, j-1:j+2]) / 9
return result
# 场景3:加密解密
def encrypt_data(data, key):
"""模拟加密操作"""
result = []
for char in data:
encrypted = (ord(char) + key) % 256
result.append(chr(encrypted))
return ''.join(result)
# 场景4:数据压缩
def compress_data(data):
"""简单的RLE压缩"""
if not data:
return []
compressed = []
count = 1
prev = data[0]
for char in data[1:]:
if char == prev:
count += 1
else:
compressed.append((prev, count))
prev = char
count = 1
compressed.append((prev, count))
return compressed
# 场景5:机器学习训练
def train_model(X, y, epochs):
"""简化的神经网络训练"""
weights = np.random.randn(X.shape[1])
lr = 0.01
for epoch in range(epochs):
# 前向传播
predictions = np.dot(X, weights)
# 计算损失
loss = np.mean((predictions - y) ** 2)
# 反向传播
gradients = 2 * np.dot(X.T, (predictions - y)) / len(y)
# 更新权重
weights -= lr * gradients
return weights
# CPU密集型任务的最佳实践:使用多进程
def optimal_cpu_bound():
"""CPU密集型任务的最佳并行方案"""
# 方案1:使用进程池
with Pool(processes=4) as pool:
# 分配任务到多个进程
results = pool.map(calculate_pi, [10000000] * 4)
avg_pi = sum(results) / len(results)
print(f"计算的π值: {avg_pi}")
# 方案2:使用numpy的向量化
# numpy内部使用C实现,自动并行
large_array = np.random.random((1000, 1000))
result = np.dot(large_array, large_array.T) # 自动优化
性能对比:
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def cpu_intensive_task(n):
"""CPU密集型:计算素数"""
def is_prime(num):
if num < 2:
return False
for i in range(2, int(num**0.5) + 1):
if num % i == 0:
return False
return True
return sum(1 for i in range(n) if is_prime(i))
def compare_cpu_bound():
n = 100000
tasks = [n] * 4
# 串行
start = time.time()
results = [cpu_intensive_task(t) for t in tasks]
serial_time = time.time() - start
# 线程池(GIL限制,性能差)
start = time.time()
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(cpu_intensive_task, tasks))
thread_time = time.time() - start
# 进程池(真正并行,性能好)
start = time.time()
with ProcessPoolExecutor(max_workers=4) as executor:
results = list(executor.map(cpu_intensive_task, tasks))
process_time = time.time() - start
print("CPU密集型任务性能对比:")
print(f"串行: {serial_time:.2f}秒")
print(f"线程池: {thread_time:.2f}秒 (提速 {serial_time/thread_time:.2f}x)")
print(f"进程池: {process_time:.2f}秒 (提速 {serial_time/process_time:.2f}x)")
if __name__ == '__main__':
compare_cpu_bound()
IO密集型任务(IO-bound)
定义: 主要时间花在等待I/O操作完成的任务,CPU大部分时间空闲。
特征:
- CPU使用率低
- 大量时间等待I/O
- 网络、磁盘等外部资源是瓶颈
- 并发可以显著提升性能
典型场景:
import time
import requests
import aiohttp
import asyncio
from concurrent.futures import ThreadPoolExecutor
import aiofiles
# 场景1:网络请求
def fetch_url_sync(url):
"""同步网络请求"""
response = requests.get(url, timeout=5)
return len(response.content)
async def fetch_url_async(session, url):
"""异步网络请求"""
async with session.get(url) as response:
content = await response.read()
return len(content)
# 场景2:文件I/O
def read_file_sync(filepath):
"""同步文件读取"""
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
async def read_file_async(filepath):
"""异步文件读取"""
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
return await f.read()
def write_file_sync(filepath, content):
"""同步文件写入"""
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
async def write_file_async(filepath, content):
"""异步文件写入"""
async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
await f.write(content)
# 场景3:数据库操作
import sqlite3
import asyncpg
def query_database_sync(query):
"""同步数据库查询"""
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return results
async def query_database_async(query):
"""异步数据库查询"""
conn = await asyncpg.connect('postgresql://localhost/db')
results = await conn.fetch(query)
await conn.close()
return results
# 场景4:API调用
class APIClient:
"""API客户端示例"""
def get_user_sync(self, user_id):
"""同步获取用户信息"""
response = requests.get(f'https://api.example.com/users/{user_id}')
return response.json()
async def get_user_async(self, session, user_id):
"""异步获取用户信息"""
async with session.get(f'https://api.example.com/users/{user_id}') as response:
return await response.json()
async def get_multiple_users(self, user_ids):
"""并发获取多个用户"""
async with aiohttp.ClientSession() as session:
tasks = [self.get_user_async(session, uid) for uid in user_ids]
return await asyncio.gather(*tasks)
# I/O密集型任务的最佳实践
async def optimal_io_bound():
"""I/O密集型任务的最佳并发方案"""
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/3',
] * 10 # 30个请求
# 方案1:使用asyncio + aiohttp
async with aiohttp.ClientSession() as session:
tasks = [fetch_url_async(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"异步完成 {len(results)} 个请求")
# 方案2:使用线程池(次优)
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(fetch_url_sync, urls))
print(f"线程池完成 {len(results)} 个请求")
# 性能对比
def compare_io_bound():
"""I/O密集型任务性能对比"""
def io_task():
"""模拟I/O操作"""
time.sleep(0.5)
return "完成"
async def async_io_task():
"""模拟异步I/O"""
await asyncio.sleep(0.5)
return "完成"
tasks_count = 20
# 串行
start = time.time()
results = [io_task() for _ in range(tasks_count)]
serial_time = time.time() - start
# 线程池
start = time.time()
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(lambda x: io_task(), range(tasks_count)))
thread_time = time.time() - start
# 协程
async def run_async():
tasks = [async_io_task() for _ in range(tasks_count)]
return await asyncio.gather(*tasks)
start = time.time()
results = asyncio.run(run_async())
async_time = time.time() - start
print("I/O密集型任务性能对比:")
print(f"串行: {serial_time:.2f}秒")
print(f"线程池: {thread_time:.2f}秒 (提速 {serial_time/thread_time:.2f}x)")
print(f"协程: {async_time:.2f}秒 (提速 {serial_time/async_time:.2f}x)")
if __name__ == '__main__':
compare_io_bound()
五、内部实现原理深度剖析
5.1 多进程内部实现原理
5.1.1 进程的创建过程(fork/spawn)
Unix/Linux系统(fork模式)
import os
import sys
def demonstrate_fork():
"""演示fork的工作原理"""
print(f"父进程开始,PID: {os.getpid()}")
# fork创建子进程
# 在父进程中返回子进程PID,在子进程中返回0
pid = os.fork()
if pid > 0:
# 父进程代码
print(f"我是父进程,PID: {os.getpid()}, 子进程PID: {pid}")
os.wait() # 等待子进程结束
else:
# 子进程代码
print(f"我是子进程,PID: {os.getpid()}, 父进程PID: {os.getppid()}")
sys.exit(0)
# 注意:fork在Windows上不可用
# fork的内部机制:
# 1. 操作系统调用fork()系统调用
# 2. 复制父进程的整个地址空间(采用写时复制COW优化)
# 3. 子进程获得父进程的代码、数据、堆、栈的副本
# 4. 两个进程拥有独立的内存空间
# 5. 文件描述符被复制(但指向同一个文件表)
写时复制(Copy-On-Write, COW)机制
初始状态(fork后):
父进程内存页 ──→ [只读共享物理内存]
↑
子进程内存页 ──────┘
写操作触发:
父进程内存页 ──→ [原物理内存页]
子进程内存页 ──→ [新复制的物理内存页]
↑ 只有在写入时才真正复制
优点:
✅ 节省内存
✅ 加快fork速度
✅ 大部分只读数据永远不需要复制
Windows系统(spawn模式)
from multiprocessing import Process, set_start_method
import os
def worker(name):
"""工作进程"""
print(f"子进程 {name}, PID: {os.getpid()}")
if __name__ == '__main__':
# Windows默认使用spawn模式
# spawn不是fork,而是启动全新的Python解释器
# 可以显式设置启动方法
# set_start_method('spawn') # Windows默认
# set_start_method('fork') # Unix/Linux默认
# set_start_method('forkserver') # 混合模式
p = Process(target=worker, args=('Worker-1',))
p.start()
p.join()
# spawn的内部机制:
# 1. 创建新的Python解释器进程
# 2. 导入必要的模块
# 3. 通过pickle序列化传递函数和参数
# 4. 在新进程中反序列化并执行
# 5. 完全独立的进程,没有共享内存
5.1.2 进程间通信(IPC)机制
1. 管道(Pipe)
from multiprocessing import Process, Pipe
import os
def pipe_internals():
"""管道内部原理演示"""
# 创建管道
parent_conn, child_conn = Pipe()
# 管道底层实现:
# Unix: 使用pipe()系统调用创建内核缓冲区
# Windows: 使用命名管道或匿名管道
def sender(conn):
print(f"发送进程 PID: {os.getpid()}")
# 数据写入管道
# 1. 序列化对象(pickle)
# 2. 写入内核缓冲区
# 3. 接收进程从缓冲区读取
conn.send({'data': 'Hello'})
conn.close()
def receiver(conn):
print(f"接收进程 PID: {os.getpid()}")
# 从管道读取
# 1. 从内核缓冲区读取字节
# 2. 反序列化为Python对象
data = conn.recv()
print(f"接收到: {data}")
conn.close()
p1 = Process(target=sender, args=(parent_conn,))
p2 = Process(target=receiver, args=(child_conn,))
p1.start()
p2.start()
p1.join()
p2.join()
# 管道的底层结构:
"""
进程A 内核空间 进程B
写端 ──→ [发送缓冲区] ──→ [管道缓冲区] ──→ [接收缓冲区] ──→ 读端
↓ ↓ ↓
pickle序列化 内核管理的FIFO pickle反序列化
特点:
- 半双工或全双工(取决于实现)
- 有限的缓冲区大小(通常64KB)
- 阻塞式读写
- 数据流式传输
"""
2. 队列(Queue)
from multiprocessing import Process, Queue
import queue
def queue_internals():
"""队列内部原理"""
q = Queue(maxsize=10)
# Queue内部结构:
# 1. 底层使用Pipe实现
# 2. 额外的线程用于读写
# 3. 使用锁保证线程安全
# 4. 使用信号量控制大小
def producer(queue):
for i in range(5):
# put内部流程:
# 1. 获取信号量(检查是否已满)
# 2. 获取锁
# 3. pickle序列化数据
# 4. 写入管道
# 5. 释放锁
# 6. 通知非空条件变量
queue.put(i)
print(f"生产: {i}")
def consumer(queue):
while True:
try:
# get内部流程:
# 1. 获取非空条件变量
# 2. 获取锁
# 3. 从管道读取
# 4. pickle反序列化
# 5. 释放锁
# 6. 释放信号量
item = queue.get(timeout=1)
print(f"消费: {item}")
except queue.Empty:
break
p1 = Process(target=producer, args=(q,))
p2 = Process(target=consumer, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
# Queue的内部架构:
"""
┌─────────────────────────────────────┐
│ multiprocessing.Queue │
├─────────────────────────────────────┤
│ _writer (Thread) │ _reader (Thread)│
│ ↓ │ ↑ │
│ ┌──────────────┴────────────┐ │
│ │ Pipe (管道) │ │
│ └───────────────────────────┘ │
│ │
│ Semaphore (信号量) - 控制容量 │
│ Lock (锁) - 保护内部状态 │
│ Condition (条件变量) - 等待/通知 │
└─────────────────────────────────────┘
工作流程:
1. put() → 序列化 → Writer线程 → Pipe → Reader线程 → 反序列化 → get()
2. 使用线程避免阻塞主进程
3. 缓冲区满时put()阻塞,空时get()阻塞
"""
3. 共享内存(Shared Memory)
from multiprocessing import Process, Value, Array, shared_memory
import numpy as np
def shared_memory_internals():
"""共享内存内部原理"""
# 方式1:Value和Array(简单类型)
# 底层使用mmap(内存映射文件)
shared_int = Value('i', 0) # 共享整数
shared_array = Array('i', [1, 2, 3, 4, 5]) # 共享数组
# 内部实现:
# 1. 在操作系统中分配共享内存段
# 2. 使用mmap映射到每个进程的地址空间
# 3. 所有进程看到同一块物理内存
# 4. 需要锁保护并发访问
def worker(val, arr):
# 访问共享内存需要获取锁
with val.get_lock():
val.value += 1
with arr.get_lock():
arr[0] += 1
processes = [Process(target=worker, args=(shared_int, shared_array)) for _ in range(10)]
for p in processes:
p.start()
for p in processes:
p.join()
print(f"共享整数: {shared_int.value}") # 10
print(f"共享数组: {list(shared_array)}") # [11, 2, 3, 4, 5]
# 方式2:SharedMemory(Python 3.8+,更高级)
shm = shared_memory.SharedMemory(create=True, size=1024)
# 共享NumPy数组
arr = np.ndarray((10,), dtype=np.int64, buffer=shm.buf)
arr[:] = range(10)
def use_shared_numpy(shm_name):
# 子进程附加到共享内存
existing_shm = shared_memory.SharedMemory(name=shm_name)
arr = np.ndarray((10,), dtype=np.int64, buffer=existing_shm.buf)
arr[:] = arr * 2 # 直接修改共享内存
existing_shm.close()
p = Process(target=use_shared_numpy, args=(shm.name,))
p.start()
p.join()
print(f"共享NumPy数组: {arr}") # [0, 2, 4, 6, ...]
shm.close()
shm.unlink()
# 共享内存的底层原理:
"""
进程A地址空间 物理内存 进程B地址空间
┌────────────┐ ┌──────────┐ ┌────────────┐
│ 虚拟地址A │─────→│ 共享内存段 │←─────│ 虚拟地址B │
└────────────┘ └──────────┘ └────────────┘
↑
同一块物理内存
实现方式(Unix/Linux):
1. System V 共享内存: shmget(), shmat()
2. POSIX 共享内存: shm_open(), mmap()
3. 内存映射文件: mmap()
实现方式(Windows):
1. CreateFileMapping()
2. MapViewOfFile()
关键特性:
✅ 最快的IPC方式(无需拷贝)
✅ 直接访问物理内存
❌ 需要同步机制(锁)
❌ 没有自动序列化
"""
5.1.3 进程池(ProcessPool)实现
from multiprocessing import Pool, cpu_count
import os
import time
class ProcessPoolInternals:
"""进程池内部机制演示"""
@staticmethod
def worker_init():
"""工作进程初始化"""
print(f"工作进程初始化: PID={os.getpid()}")
@staticmethod
def demonstrate_pool():
"""进程池工作原理"""
# 创建进程池
with Pool(processes=4, initializer=ProcessPoolInternals.worker_init) as pool:
# 进程池内部结构:
# 1. 创建固定数量的工作进程(预先fork)
# 2. 维护任务队列(输入队列)
# 3. 维护结果队列(输出队列)
# 4. 主进程分发任务到工作进程
# 5. 工作进程从任务队列获取任务
# 6. 执行完成后将结果放入结果队列
def task(x):
time.sleep(0.1)
return x * x
# map内部流程:
# 1. 将任务分成多个chunk
# 2. 每个chunk作为一个任务放入任务队列
# 3. 工作进程并行处理
# 4. 收集所有结果并按顺序返回
results = pool.map(task, range(10))
print(f"结果: {results}")
# 进程池的内部架构:
"""
主进程
│
┌─────────────┼─────────────┐
│ │ │
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│工作进程1│ │工作进程2│ │工作进程3│
└────────┘ └────────┘ └────────┘
↑ ↑ ↑
└─────────────┴─────────────┘
任务队列
┌─────────────────────────┐
│ [任务1] [任务2] [任务3] │
└─────────────────────────┘
结果队列
┌─────────────────────────┐
│ [结果1] [结果2] [结果3] │
└─────────────────────────┘
工作流程:
1. 初始化时预创建N个工作进程
2. 主进程将任务序列化后放入任务队列
3. 空闲的工作进程从队列获取任务
4. 执行任务并将结果放入结果队列
5. 主进程收集结果
6. 进程复用,避免频繁创建销毁
优化策略:
- Chunk分块:减少任务分发开销
- 进程复用:避免创建销毁开销
- 缓冲队列:平衡生产消费速度
"""
5.2 多线程内部实现原理
2.1 GIL(全局解释器锁)深入解析
GIL的本质
import threading
import sys
import dis
def demonstrate_gil():
"""演示GIL的影响"""
# GIL是CPython解释器级别的互斥锁
# 作用:保护Python对象的引用计数不被竞态条件破坏
counter = 0
def increment():
global counter
# 这个简单操作在字节码层面不是原子的
for _ in range(1000000):
counter += 1
# 查看字节码
print("字节码分析:")
dis.dis(increment)
# counter += 1 对应的字节码(简化):
# LOAD_GLOBAL counter # 1. 读取counter的值
# LOAD_CONST 1 # 2. 加载常量1
# BINARY_ADD # 3. 执行加法
# STORE_GLOBAL counter # 4. 存储结果
# 这4条指令之间可能发生线程切换!
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终值: {counter}") # 可能不是2000000!
# GIL的工作机制:
"""
时间线:
Thread 1: [获取GIL]──[执行Python代码]──[释放GIL]──[等待]──[获取GIL]──...
Thread 2: [等待GIL]──────────────────[获取GIL]──[执行]──[释放]──...
GIL释放时机:
1. 执行固定数量的字节码指令(默认100条,Python 3中改为检查机制)
2. I/O操作时(如read(), write(), sleep())
3. 调用C扩展(如果扩展释放GIL)
4. 长时间运行的操作(如time.sleep())
检查机制(Python 3.2+):
- 不再按指令数,改为按时间间隔(默认5ms)
- 使用gil_drop_request标志位
- 等待线程可以请求当前线程释放GIL
- 减少了不必要的上下文切换
伪代码:
while True:
获取GIL
执行Python字节码
if 时间超过5ms or gil_drop_request:
释放GIL
等待其他线程
重新竞争GIL
"""
GIL的底层实现(C代码级别)
// Python/ceval_gil.h (简化版本)
struct _gil_runtime_state {
unsigned long interval; // 检查间隔(纳秒)
_Py_atomic_int locked; // GIL是否被持有
unsigned long switch_number; // 切换计数
// 条件变量和互斥锁
pthread_cond_t cond; // 等待条件
pthread_mutex_t mutex; // 保护GIL状态
pthread_cond_t switch_cond; // 切换条件
pthread_mutex_t switch_mutex; // 切换互斥锁
};
// 获取GIL
static void take_gil(PyThreadState *tstate) {
pthread_mutex_lock(&gil.mutex);
while (gil.locked) {
// GIL被其他线程持有,等待
pthread_cond_wait(&gil.cond, &gil.mutex);
}
// 获取GIL
gil.locked = 1;
pthread_mutex_unlock(&gil.mutex);
}
// 释放GIL
static void drop_gil(PyThreadState *tstate) {
pthread_mutex_lock(&gil.mutex);
gil.locked = 0;
// 唤醒等待的线程
pthread_cond_signal(&gil.cond);
pthread_mutex_unlock(&gil.mutex);
}
绕过GIL的方法
import ctypes
import numpy as np
from numba import jit, prange
def bypass_gil_methods():
"""绕过GIL的各种方法"""
# 方法1:使用释放GIL的C扩展
# NumPy在底层计算时释放GIL
def numpy_computation():
arr1 = np.random.random((1000, 1000))
arr2 = np.random.random((1000, 1000))
# 这个操作会释放GIL
result = np.dot(arr1, arr2)
return result
# 方法2:使用Numba编译(带nogil选项)
@jit(nopython=True, nogil=True)
def numba_computation(n):
# 这个函数编译为机器码,不需要GIL
result = 0
for i in prange(n):
result += i * i
return result
# 方法3:使用ctypes调用C函数
libc = ctypes.CDLL(None)
# C的sleep不需要GIL
def c_sleep(seconds):
libc.sleep(seconds)
# 方法4:使用threading配合I/O操作
# I/O操作会自动释放GIL
import time
def io_operation():
time.sleep(1) # 释放GIL
with open('file.txt', 'r') as f:
data = f.read() # 释放GIL
# GIL释放示例图:
"""
线程1执行NumPy操作:
[持有GIL]──[调用NumPy]──[释放GIL]──[C代码执行]──[重新获取GIL]──[返回]
↓
线程2可以并行执行: [获取GIL]──[执行Python代码]──[释放GIL]
结果:虽然是线程,但NumPy操作可以真正并行!
"""
2.2 线程调度与上下文切换
import threading
import time
import os
def thread_scheduling_demo():
"""线程调度演示"""
class ThreadSchedulingMonitor:
def __init__(self):
self.switch_count = 0
self.lock = threading.Lock()
def cpu_bound_task(self, thread_id):
"""CPU密集型任务"""
last_time = time.time()
for i in range(10):
# 模拟计算
result = sum(j * j for j in range(100000))
current_time = time.time()
if current_time - last_time > 0.005: # 检测到可能的上下文切换
with self.lock:
self.switch_count += 1
print(f"线程{thread_id}: 可能发生了上下文切换")
last_time = current_time
monitor = ThreadSchedulingMonitor()
threads = [
threading.Thread(target=monitor.cpu_bound_task, args=(i,))
for i in range(4)
]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"总切换次数: {monitor.switch_count}")
# 上下文切换的内容:
"""
保存当前线程状态:
1. CPU寄存器值(PC、SP、通用寄存器)
2. 线程栈信息
3. 线程局部存储(TLS)
4. GIL状态
恢复新线程状态:
1. 加载保存的寄存器值
2. 切换栈指针
3. 恢复TLS
4. 获取GIL(如果是Python线程)
开销分析:
- 直接开销:保存/恢复寄存器(纳秒级)
- 间接开销:
* CPU缓存失效(微秒级)
* TLB刷新(微秒级)
* 流水线停顿
* GIL竞争(Python特有,微秒到毫秒级)
┌─────────────────────────────────────────┐
│ 线程上下文切换时间线 │
├─────────────────────────────────────────┤
│ T1执行 → 保存T1 → 调度决策 → 恢复T2 → T2执行 │
│ [5μs] [1μs] [2μs] [1μs] [5μs] │
│ │
│ 总开销:约4μs + 缓存失效开销 │
└─────────────────────────────────────────┘
"""
2.3 线程同步原语的底层实现
import threading
import time
class LockInternals:
"""锁的内部实现原理"""
@staticmethod
def demonstrate_lock_internals():
"""演示锁的工作原理"""
# threading.Lock底层使用pthread_mutex_t(Unix)或CRITICAL_SECTION(Windows)
lock = threading.Lock()
# Lock的内部状态:
# - locked: bool(是否已被获取)
# - owner: thread_id(持有锁的线程ID)
# - waiters: queue(等待队列)
def worker(worker_id):
print(f"线程{worker_id}尝试获取锁...")
# lock.acquire()的内部流程:
# 1. 原子操作检查locked标志
# 2. 如果未锁定,设置locked=True,设置owner=当前线程
# 3. 如果已锁定,将当前线程加入等待队列
# 4. 调用pthread_cond_wait()等待唤醒
# 5. 被唤醒后重新竞争锁
lock.acquire()
print(f"线程{worker_id}获得锁")
time.sleep(0.1)
# lock.release()的内部流程:
# 1. 检查owner是否是当前线程
# 2. 设置locked=False
# 3. 从等待队列唤醒一个线程(pthread_cond_signal)
# 4. 被唤醒的线程竞争锁
lock.release()
print(f"线程{worker_id}释放锁")
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
# Lock的底层实现(伪代码):
"""
class Lock:
def __init__(self):
self.locked = False
self.owner = None
self.waiters = Queue()
self.mutex = pthread_mutex_t() # 保护内部状态
self.cond = pthread_cond_t() # 条件变量
def acquire(self):
pthread_mutex_lock(&self.mutex)
while self.locked:
# 加入等待队列
self.waiters.push(current_thread)
# 等待条件变量(自动释放mutex)
pthread_cond_wait(&self.cond, &self.mutex)
# 获取锁
self.locked = True
self.owner = current_thread
pthread_mutex_unlock(&self.mutex)
def release(self):
pthread_mutex_lock(&self.mutex)
if self.owner != current_thread:
raise RuntimeError("释放未持有的锁")
self.locked = False
self.owner = None
# 唤醒一个等待线程
if not self.waiters.empty():
pthread_cond_signal(&self.cond)
pthread_mutex_unlock(&self.mutex)
"""
class ConditionInternals:
"""条件变量的内部实现"""
@staticmethod
def demonstrate_condition():
"""演示条件变量"""
condition = threading.Condition()
items = []
def producer():
for i in range(5):
time.sleep(0.5)
# 条件变量的典型用法
with condition:
items.append(i)
print(f"生产: {i}")
# notify()内部:
# 1. 从等待队列中选择一个线程
# 2. 将其移到就绪队列
# 3. 发送信号唤醒
condition.notify()
def consumer():
while True:
with condition:
# wait()内部:
# 1. 释放锁
# 2. 将当前线程加入等待队列
# 3. 等待被notify唤醒
# 4. 被唤醒后重新获取锁
while not items:
condition.wait()
item = items.pop(0)
print(f"消费: {item}")
if item == 4:
break
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t2.start()
time.sleep(0.1)
t1.start()
t1.join()
t2.join()
# 条件变量的工作流程:
"""
消费者线程:
1. 获取锁
2. 检查条件(队列是否为空)
3. 如果条件不满足,调用wait():
a. 释放锁
b. 进入等待状态
c. 被唤醒后重新获取锁
d. 重新检查条件
4. 条件满足,执行操作
5. 释放锁
生产者线程:
1. 获取锁
2. 修改共享状态(放入数据)
3. 调用notify()唤醒等待线程
4. 释放锁
时间线:
消费者: [获取锁]─[检查空]─[wait释放锁]─────[被唤醒]─[重新获取锁]─[消费]
生产者: ─────────────────[获取锁]─[生产]─[notify]─[释放锁]
"""
5.3 协程内部实现原理
5.3.1 事件循环(Event Loop)核心机制
import asyncio
import selectors
import socket
class EventLoopInternals:
"""事件循环内部原理演示"""
@staticmethod
def simple_event_loop():
"""简化的事件循环实现"""
# 事件循环的核心组件
class SimpleEventLoop:
def __init__(self):
# 选择器:监听I/O事件
self.selector = selectors.DefaultSelector()
# 就绪队列:可以立即执行的协程
self.ready = []
# 调度队列:延迟执行的协程
self.scheduled = []
# 当前时间
self.time = 0
def create_task(self, coro):
"""创建任务"""
task = Task(coro, self)
self.ready.append(task)
return task
def call_soon(self, callback, *args):
"""尽快调用回调"""
self.ready.append((callback, args))
def call_later(self, delay, callback, *args):
"""延迟调用"""
when = self.time + delay
self.scheduled.append((when, callback, args))
self.scheduled.sort() # 按时间排序
def run_forever(self):
"""运行事件循环"""
while True:
# 1. 处理就绪队列
while self.ready:
callback = self.ready.pop(0)
if isinstance(callback, Task):
callback.step()
else:
func, args = callback
func(*args)
# 2. 处理调度队列
self.time = time.time()
while self.scheduled and self.scheduled[0][0] <= self.time:
when, callback, args = self.scheduled.pop(0)
callback(*args)
# 3. 等待I/O事件
timeout = None
if self.scheduled:
timeout = max(0, self.scheduled[0][0] - self.time)
# select()系统调用:等待I/O就绪
events = self.selector.select(timeout)
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
# 如果没有任务,退出
if not self.ready and not self.scheduled:
break
# 简单的Task实现
class Task:
def __init__(self, coro, loop):
self.coro = coro
self.loop = loop
def step(self):
try:
# 恢复协程执行
result = self.coro.send(None)
# 如果返回值,说明还没完成
if result is not None:
self.loop.ready.append(self)
except StopIteration:
# 协程完成
pass
# 使用示例
async def my_coroutine():
print("协程开始")
await asyncio.sleep(1)
print("协程结束")
loop = SimpleEventLoop()
loop.create_task(my_coroutine())
loop.run_forever()
# 事件循环的完整架构:
"""
┌─────────────────────────────────────────────────────────┐
│ Event Loop (事件循环) │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌────────────┐ ┌─────────────┐ │
│ │ Ready Queue│ │Scheduled Q │ │ Selector │ │
│ │ (就绪队列) │ │ (调度队列) │ │ (I/O多路复用) │ │
│ └────────────┘ └────────────┘ └─────────────┘ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Main Loop (主循环) │ │
│ ├──────────────────────────────────────────────┤ │
│ │ 1. 执行就绪任务 │ │
│ │ 2. 执行到期的调度任务 │ │
│ │ 3. 等待I/O事件 (select/epoll/kqueue) │ │
│ │ 4. 处理I/O回调 │ │
│ │ 5. 重复 │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
关键系统调用:
- Unix: select(), poll(), epoll_wait()
- Windows: select(), IOCP
- macOS: kqueue()
工作流程:
1. 应用创建协程
2. 协程注册到事件循环
3. 事件循环调度协程执行
4. 协程遇到await,挂起并返回控制权
5. 事件循环继续执行其他协程
6. I/O完成时,通过回调恢复协程
"""
3.2 协程的状态机与调度
import asyncio
import inspect
class CoroutineInternals:
"""协程内部状态演示"""
@staticmethod
async def demonstrate_coroutine_states():
"""演示协程的状态"""
# 协程的生命周期状态:
# CORO_CREATED → CORO_RUNNING → CORO_SUSPENDED → CORO_CLOSED
# (创建) (运行中) (挂起) (关闭)
async def example_coroutine():
print("协程开始")
# 执行await时,协程从RUNNING变为SUSPENDED
await asyncio.sleep(1)
print("协程恢复")
return "完成"
# 创建协程对象(CREATED状态)
coro = example_coroutine()
print(f"协程对象: {coro}")
print(f"协程状态: {inspect.getcoroutinestate(coro)}") # CORO_CREATED
# 运行协程
task = asyncio.create_task(coro)
result = await task
print(f"协程结果: {result}")
# 协程对象的内部结构(CPython):
"""
typedef struct {
PyObject_HEAD
PyCodeObject *co_code; // 代码对象
PyObject *co_name; // 协程名称
PyFrameObject *co_frame; // 执行帧
char co_flags; // 标志位
PyObject *co_await; // 当前等待的awaitable
int co_running; // 是否正在运行
PyObject *co_origin; // 创建位置
} PyCoroObject;
协程帧(Frame):
- 局部变量
- 执行栈
- 指令指针(下次恢复执行的位置)
- 返回值栈
"""
class CoroutineScheduling:
"""协程调度机制"""
@staticmethod
async def demonstrate_scheduling():
"""演示协程调度"""
async def task_a():
print("Task A 开始")
for i in range(3):
print(f"Task A step {i}")
# yield控制权给事件循环
await asyncio.sleep(0)
print("Task A 完成")
async def task_b():
print("Task B 开始")
for i in range(3):
print(f"Task B step {i}")
await asyncio.sleep(0)
print("Task B 完成")
# 并发执行两个任务
await asyncio.gather(task_a(), task_b())
# 输出顺序(协程调度):
# Task A 开始
# Task A step 0
# Task B 开始
# Task B step 0
# Task A step 1
# Task B step 1
# Task A step 2
# Task B step 2
# Task A 完成
# Task B 完成
# 协程调度的时间线:
"""
时间 →
Task A: [开始]─[step0]─[await]─────[恢复]─[step1]─[await]─────[恢复]─[step2]─[完成]
↓yield ↓yield ↓
Event Loop: [切换]──────────────────[切换]──────────────────[切换]
↓ ↓
Task B: [开始]────────[step0]─[await]─────[恢复]─[step1]─[await]─────[恢复]─[完成]
关键点:
1. await关键字让出控制权
2. 事件循环决定下一个执行哪个协程
3. 所有操作在单线程中,无需锁
4. 协作式多任务(非抢占式)
"""
3.3 async/await的底层实现
import sys
import types
class AsyncAwaitInternals:
"""async/await的底层原理"""
@staticmethod
def manual_coroutine_implementation():
"""手动实现协程(不使用async/await)"""
# async def本质上是创建一个生成器
# 但返回的是coroutine对象而不是generator对象
# 手动实现一个简单的awaitable
class Awaitable:
def __await__(self):
# __await__必须返回一个迭代器
yield # 让出控制权
return "结果"
# 手动实现协程
def manual_coroutine():
"""
这个函数等价于:
async def manual_coroutine():
result = await Awaitable()
return result
"""
# 创建awaitable
awaitable = Awaitable()
# 获取迭代器
iterator = awaitable.__await__()
# 驱动迭代器
try:
while True:
# send(None)相当于next()
iterator.send(None)
except StopIteration as e:
# 返回值在StopIteration的value中
result = e.value
return result
# 实际的async def编译过程:
"""
源代码:
async def foo():
x = await bar()
return x
编译后(简化):
def foo():
# 创建协程对象
coro = PyCoroObject()
coro.code = <foo的字节码>
# 设置协程标志
coro.flags = CO_COROUTINE | CO_ITERABLE_COROUTINE
return coro
执行await时的字节码:
LOAD_NAME bar
CALL_FUNCTION 0
GET_AWAITABLE # 获取awaitable对象
LOAD_CONST None
YIELD_FROM # 委托给awaitable的迭代器
STORE_NAME x
"""
# async/await的类型层次:
"""
Coroutine (协程)
├─ 实现 __await__() 方法
└─ 返回迭代器
Awaitable (可等待对象)
├─ Coroutine
├─ Task
└─ Future
Generator (生成器)
├─ 实现 __iter__() 和 __next__()
└─ 使用 yield
关系:
- async def 创建 Coroutine
- await 只能用于 Awaitable
- Coroutine 是特殊的 Generator
- yield from 可以委托给 Generator
- await 本质上是 yield from 的语法糖(但类型检查更严格)
"""
class FutureAndTask:
"""Future和Task的内部实现"""
@staticmethod
def demonstrate_future_internals():
"""演示Future的内部机制"""
class SimpleFuture:
"""简化的Future实现"""
def __init__(self, loop=None):
self._loop = loop or asyncio.get_event_loop()
self._state = 'PENDING' # PENDING, CANCELLED, FINISHED
self._result = None
self._exception = None
self._callbacks = [] # 完成时的回调列表
def set_result(self, result):
"""设置结果"""
if self._state != 'PENDING':
raise RuntimeError('Future已完成')
self._result = result
self._state = 'FINISHED'
self._schedule_callbacks()
def set_exception(self, exception):
"""设置异常"""
if self._state != 'PENDING':
raise RuntimeError('Future已完成')
self._exception = exception
self._state = 'FINISHED'
self._schedule_callbacks()
def add_done_callback(self, callback):
"""添加完成回调"""
if self._state == 'FINISHED':
# 已完成,立即调度回调
self._loop.call_soon(callback, self)
else:
self._callbacks.append(callback)
def _schedule_callbacks(self):
"""调度所有回调"""
for callback in self._callbacks:
self._loop.call_soon(callback, self)
self._callbacks.clear()
def __await__(self):
"""实现awaitable协议"""
if self._state == 'FINISHED':
if self._exception:
raise self._exception
return self._result
# 让出控制权,等待结果
yield self # 返回Future自己给事件循环
# 恢复时检查结果
if self._exception:
raise self._exception
return self._result
# Task是Future的子类
class SimpleTask(SimpleFuture):
"""简化的Task实现"""
def __init__(self, coro, loop=None):
super().__init__(loop)
self._coro = coro # 要执行的协程
self._loop.call_soon(self._step) # 立即调度第一步
def _step(self, exc=None):
"""执行协程的一步"""
try:
if exc is None:
# 恢复协程执行
result = self._coro.send(None)
else:
# 抛出异常到协程
result = self._coro.throw(exc)
# 协程返回了一个Future,等待它完成
if isinstance(result, SimpleFuture):
result.add_done_callback(self._wakeup)
else:
# 继续执行
self._loop.call_soon(self._step)
except StopIteration as e:
# 协程完成
self.set_result(e.value)
except Exception as e:
# 协程抛出异常
self.set_exception(e)
def _wakeup(self, future):
"""被等待的Future完成时调用"""
try:
result = future._result
self._step()
except Exception as e:
self._step(e)
# Future和Task的关系:
"""
Future: 代表一个尚未完成的操作
├─ _state: 状态(PENDING/FINISHED/CANCELLED)
├─ _result: 结果
├─ _exception: 异常
├─ _callbacks: 回调列表
└─ 方法: set_result(), set_exception(), add_done_callback()
Task: Future的子类,包装一个协程
├─ 继承Future的所有特性
├─ _coro: 被包装的协程
├─ _step(): 驱动协程执行
└─ _wakeup(): Future完成时恢复协程
工作流程:
1. Task创建时调度_step()
2. _step()执行协程直到遇到await
3. await返回一个Future
4. 将_wakeup()注册为Future的回调
5. 当Future完成时,_wakeup()被调用
6. _wakeup()再次调用_step()
7. 重复直到协程完成
"""
3.4 I/O多路复用的底层实现
import select
import socket
import asyncio
class IOMultiplexing:
"""I/O多路复用原理"""
@staticmethod
def demonstrate_select():
"""演示select的使用"""
# 创建服务器socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server.bind(('localhost', 8888))
server.listen(5)
# 要监听的socket列表
inputs = [server]
outputs = []
print("服务器启动,使用select监听...")
while inputs:
# select系统调用
# 参数:读列表,写列表,异常列表,超时
# 返回:就绪的读列表,就绪的写列表,异常列表
readable, writable, exceptional = select.select(
inputs, outputs, inputs, 1.0
)
# 处理可读socket
for s in readable:
if s is server:
# 接受新连接
client, addr = s.accept()
client.setblocking(False)
inputs.append(client)
print(f"新连接: {addr}")
else:
# 读取数据
data = s.recv(1024)
if data:
print(f"收到数据: {data}")
outputs.append(s)
else:
# 连接关闭
inputs.remove(s)
s.close()
# 处理可写socket
for s in writable:
s.send(b"Echo: OK\n")
outputs.remove(s)
# 处理异常
for s in exceptional:
inputs.remove(s)
s.close()
# select/epoll/kqueue 对比:
"""
1. select (所有平台)
原理:
- 将要监听的fd_set传递给内核
- 内核遍历所有文件描述符,检查是否就绪
- 返回就绪的文件描述符集合
伪代码:
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(socket_fd, &readfds);
select(max_fd+1, &readfds, NULL, NULL, &timeout);
if (FD_ISSET(socket_fd, &readfds)) {
// socket可读
}
限制:
❌ fd数量限制(通常1024)
❌ 每次调用需要复制整个fd_set
❌ O(n)复杂度,遍历所有fd
❌ 性能随fd数量线性下降
2. poll (Unix/Linux)
改进:
✅ 无fd数量限制
❌ 仍然是O(n)复杂度
struct pollfd fds[2];
fds[0].fd = socket1;
fds[0].events = POLLIN; // 监听可读
poll(fds, 2, timeout);
3. epoll (Linux)
原理:
- 在内核中维护一个红黑树存储fd
- 使用事件驱动,而不是轮询
- 就绪的fd放入就绪列表
伪代码:
epfd = epoll_create(10);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = socket_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &ev);
struct epoll_event events[10];
nfds = epoll_wait(epfd, events, 10, timeout);
for (i = 0; i < nfds; i++) {
// 处理events[i]
}
优势:
✅ O(1)复杂度(只返回就绪的fd)
✅ 无fd数量限制
✅ 边缘触发和水平触发模式
✅ 性能不随fd数量下降
4. kqueue (BSD/macOS)
类似epoll,BSD系统的实现
kq = kqueue();
struct kevent ev;
EV_SET(&ev, socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &ev, 1, NULL, 0, NULL);
struct kevent events[10];
nfds = kevent(kq, NULL, 0, events, 10, &timeout);
"""
class AsyncIOImplementation:
"""asyncio的I/O实现"""
@staticmethod
async def demonstrate_asyncio_io():
"""演示asyncio的I/O处理"""
# asyncio在不同平台选择不同的selector
# Linux: epoll
# Windows: IOCP (select)
# macOS: kqueue
async def fetch_url(url):
# 异步HTTP请求的内部流程:
# 1. 创建socket
# sock = socket.socket()
# sock.setblocking(False)
# 2. 连接(非阻塞)
# try:
# sock.connect(addr)
# except BlockingIOError:
# pass # 连接中
# 3. 注册到selector,等待连接完成
# selector.register(sock, EVENT_WRITE, callback)
# 4. 事件循环调用selector.select()
# events = selector.select(timeout)
# 5. socket可写时,连接完成
# callback被调用,恢复协程
# 6. 发送请求
# await sock.sendall(request)
# 7. 注册读事件,等待响应
# selector.modify(sock, EVENT_READ, callback)
# 8. socket可读时,接收数据
# data = await sock.recv(4096)
reader, writer = await asyncio.open_connection('httpbin.org', 80)
request = b'GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'
writer.write(request)
await writer.drain() # 等待发送完成
response = await reader.read(1024)
print(f"收到响应: {len(response)} 字节")
writer.close()
await writer.wait_closed()
# asyncio的完整I/O栈:
"""
应用层
↓
async/await 协程
↓
asyncio.Task
↓
asyncio.Future
↓
asyncio.EventLoop
↓
selectors.Selector (epoll/kqueue/select)
↓
操作系统内核
↓
网络/磁盘驱动
↓
硬件设备
数据流向:
1. 应用发起I/O操作(如read)
2. 创建Future对象
3. 注册fd到selector
4. 协程yield,让出控制权
5. 事件循环调用selector.select()等待
6. fd就绪时,selector返回
7. 回调被调用,设置Future结果
8. 协程被恢复
9. 返回数据给应用
零拷贝技术:
某些场景下(如sendfile),数据直接在内核空间传输
不需要复制到用户空间,大幅提升性能
"""
六、为什么能实现并发与并行?底层原理深度剖析
6.1 问题的本质:什么是并发与并行?
在深入探讨之前,我们需要理解一个核心问题:为什么单个CPU核心在同一时刻只能执行一条指令,却能让我们感觉多个任务在"同时"运行?
6.1.1 CPU的物理限制
┌─────────────────────────────────────────────────────────────┐
│ 单核CPU的执行单元 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 取指 │───▶│ 译码 │───▶│ 执行 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 在任意时刻t,只能执行一条机器指令 │
│ 物理限制:ALU(算术逻辑单元)同一时刻只能处理一个操作 │
└─────────────────────────────────────────────────────────────┘
关键认知:
- 物理事实: 单个CPU核心在纳秒级别的任意时刻,只能执行一条指令
- 并发的本质: 通过时间分片和快速切换,让多个任务轮流使用CPU
- 并行的本质: 拥有多个物理执行单元(多核),真正同时执行多个任务
6.2 进程为什么能实现并行与并发?
6.2.1 操作系统层面的根本原因
进程能实现并发/并行,源于操作系统的进程调度机制和硬件的多核支持。
// Linux内核中的进程调度核心数据结构
struct task_struct {
// 进程状态
volatile long state; // TASK_RUNNING, TASK_INTERRUPTIBLE, etc.
// CPU上下文(寄存器快照)
struct thread_struct thread; // 保存CPU寄存器值
// 内存管理
struct mm_struct *mm; // 独立的虚拟地址空间
// 调度信息
int prio; // 优先级
unsigned int policy; // 调度策略
unsigned int time_slice; // 时间片
// 进程关系
struct task_struct *parent;
struct list_head children;
};
为什么进程能并发执行?
- 时间分片机制:
时间轴: |--P1--|--P2--|--P3--|--P1--|--P2--|--P3--|...
0 10ms 20ms 30ms 40ms 50ms 60ms
操作系统通过定时器中断(Timer Interrupt),每隔几毫秒强制进行进程切换:
1. 定时器中断触发(如每10ms)
2. 保存当前进程上下文(CPU寄存器、程序计数器等)
3. 选择下一个要运行的进程(调度算法)
4. 恢复目标进程的上下文
5. 跳转到目标进程继续执行
- 上下文切换的硬件支持:
; x86-64架构的上下文切换伪代码
context_switch:
; 保存当前进程的寄存器
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push rbp
push r8-r15
; 保存当前栈指针到进程控制块
mov [old_process.rsp], rsp
; 切换到新进程的栈
mov rsp, [new_process.rsp]
; 恢复新进程的寄存器
pop r15-r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
; 跳转到新进程
ret
为什么进程能并行执行?
多核CPU架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Core 0 │ │ Core 1 │ │ Core 2 │ │ Core 3 │
│ 执行进程P1 │ │ 执行进程P2 │ │ 执行进程P3 │ │ 执行进程P4 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
└────────────────┴────────────────┴────────────────┘
共享L3缓存
│
┌─────┴─────┐
│ 主内存 │
└───────────┘
关键点:
1. 每个CPU核心有独立的执行单元(ALU、寄存器等)
2. 操作系统调度器将不同进程分配到不同核心
3. 多个核心真正同时执行不同进程的指令
4. 通过CPU亲和性(CPU Affinity)绑定进程到特定核心
6.2.2 Python多进程并行的实现
import os
import multiprocessing
import time
def cpu_bound_task(n):
"""CPU密集型任务"""
print(f"进程 {os.getpid()} 在 CPU核心 {os.sched_getaffinity(0)} 上运行")
result = sum(i * i for i in range(n))
return result
if __name__ == "__main__":
# 底层发生了什么?
start = time.time()
# 1. 创建进程池 - 调用fork()系统调用
with multiprocessing.Pool(4) as pool:
# 2. 每个子进程获得独立的:
# - 虚拟地址空间(mm_struct)
# - 文件描述符表(files_struct)
# - 信号处理表(signal_struct)
# - CPU上下文(thread_struct)
# 3. 操作系统调度器将4个进程分配到4个CPU核心
# 真正实现物理并行
results = pool.map(cpu_bound_task, [10000000] * 4)
print(f"耗时: {time.time() - start:.2f}秒")
# 在4核CPU上,耗时约为单线程的1/4
# 因为4个进程在4个核心上真正同时执行
为什么Python多进程能绕过GIL实现真并行?
进程间的内存隔离:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 进程P1 │ │ 进程P2 │ │ 进程P3 │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Python解释│ │ │ │ Python解释│ │ │ │ Python解释│ │
│ │ 器实例 │ │ │ │ 器实例 │ │ │ │ 器实例 │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ GIL-1 │ │ │ │ GIL-2 │ │ │ │ GIL-3 │ │
│ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │
│ │ │ │ │ │
│ 虚拟地址空间1 │ │ 虚拟地址空间2 │ │ 虚拟地址空间3 │
└──────────────────┘ └──────────────────┘ └──────────────────┘
关键点:
1. 每个进程有独立的Python解释器实例
2. 每个解释器有自己的GIL,互不干扰
3. GIL只限制单个进程内的线程并行
4. 多个进程可以在多核上真正并行执行Python字节码
6.3 线程为什么能实现并发?
6.3.1 线程的本质:轻量级进程
// Linux内核中,线程实际上也是task_struct
// 但多个线程共享同一个进程的资源
// 进程P创建线程T的过程:
clone(CLONE_VM | // 共享虚拟地址空间
CLONE_FS | // 共享文件系统信息
CLONE_FILES | // 共享文件描述符表
CLONE_SIGHAND | // 共享信号处理
CLONE_THREAD); // 线程标志
// 结果:
// 线程T和进程P共享:
// - 代码段、数据段、堆
// - 全局变量
// - 文件描述符
// - 信号处理器
// 线程T独有:
// - 线程ID(TID)
// - 栈空间(thread_struct.sp指向独立的栈)
// - 寄存器上下文
// - 线程局部存储(TLS)
为什么线程能并发?
线程调度与进程调度本质相同:
时间轴视图:
|--T1--|--T2--|--T3--|--T1--|--T2--|--T3--|
0 5ms 10ms 15ms 20ms 25ms 30ms
底层机制:
1. 定时器中断(如每5ms)触发
2. 保存当前线程上下文(栈指针、寄存器)
3. 调度算法选择下一个线程
4. 恢复目标线程上下文
5. CPU继续执行目标线程
与进程切换的区别:
- 更快:不需要切换虚拟地址空间(不需要刷新TLB)
- 更轻:共享内存,上下文数据更少
- 开销:约5-10微秒(进程切换约20-100微秒)
6.3.2 为什么Python线程不能并行(GIL的限制)?
import threading
import sys
# GIL的实现原理(CPython源码简化)
class GIL:
"""GIL的简化模型"""
def __init__(self):
self.locked = 0 # 0=未锁定, 1=锁定
self.switch_interval = 0.005 # 5毫秒切换间隔
def acquire(self, thread_id):
"""线程获取GIL"""
while True:
# 原子操作:比较并交换
if compare_and_swap(&self.locked, 0, 1):
print(f"线程{thread_id}获得GIL")
return
else:
# GIL被占用,等待信号
wait_for_signal()
def release(self, thread_id):
"""线程释放GIL"""
print(f"线程{thread_id}释放GIL")
self.locked = 0
# 唤醒一个等待的线程
signal_one_waiting_thread()
# Python字节码执行循环(PyEval_EvalFrameEx的简化版本)
def eval_frame(frame, gil):
"""执行Python字节码"""
ticks = 0 # 执行的字节码指令计数
while True:
# 每执行一定数量的字节码指令就检查GIL
if ticks >= 100: # sys.getswitchinterval() * 1000
# 释放GIL,给其他线程机会
gil.release(current_thread_id())
# 立即尝试重新获取GIL
gil.acquire(current_thread_id())
ticks = 0
# 执行一条字节码指令
opcode = frame.code[frame.pc]
execute_opcode(opcode)
ticks += 1
GIL导致的执行流程:
双核CPU上运行两个Python线程:
时间轴:
Core 0: |--T1(持有GIL)--|--T1等待--|--T1(持有GIL)--|
Core 1: |--T2等待GIL---|--T2(持有GIL)--|--T2等待--|
详细分解:
t=0ms: 线程T1获得GIL,在Core 0上执行Python字节码
线程T2在Core 1上自旋等待GIL
t=5ms: T1执行了100条字节码,检查到需要释放GIL
T1释放GIL并立即尝试重新获取
T2抢到GIL,开始在Core 1上执行
T1在Core 0上等待GIL
t=10ms: T2释放GIL
T1或T2抢到GIL,继续循环...
关键点:
1. 任意时刻,只有一个线程持有GIL
2. 只有持有GIL的线程能执行Python字节码
3. 即使有多个CPU核心,也只能有一个核心执行Python代码
4. 多线程在CPU密集型任务上甚至比单线程更慢(上下文切换开销)
线程在什么情况下能并发?
import threading
import time
import requests
def io_task():
"""I/O密集型任务"""
response = requests.get("https://api.github.com")
# 在等待网络响应期间:
# 1. 线程主动释放GIL(调用了C扩展的阻塞函数)
# 2. 操作系统将线程置于阻塞状态
# 3. CPU调度其他线程执行
# 4. 网络数据到达后,线程被唤醒并重新获取GIL
return len(response.text)
# 虽然有GIL,但I/O等待期间会释放GIL,所以能并发
threads = [threading.Thread(target=io_task) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
6.4 协程为什么能实现并发?
6.4.1 协程的本质:用户态调度
传统线程(内核态调度):
┌──────────────────────────────────────────────┐
│ 用户态(User Space) │
│ Thread1 Thread2 Thread3 │
└──────────────┬───────────────────────────────┘
│ 系统调用(System Call)
┌──────────────▼───────────────────────────────┐
│ 内核态(Kernel Space) │
│ 操作系统调度器(Scheduler) │
│ - 定时器中断 │
│ - 上下文切换 │
│ - 调度算法(CFS等) │
└──────────────────────────────────────────────┘
协程(用户态调度):
┌──────────────────────────────────────────────┐
│ 用户态(User Space) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Event Loop(事件循环) │ │
│ │ 协程调度器(用户实现) │ │
│ └────────────────────────────────────┘ │
│ │
│ Coroutine1 Coroutine2 Coroutine3 │
│ (挂起) (运行) (挂起) │
└──────────────────────────────────────────────┘
│ 只在I/O时才调用系统调用
▼
┌──────────────────────────────────────────────┐
│ 内核态(Kernel Space) │
│ epoll/kqueue(I/O多路复用) │
└──────────────────────────────────────────────┘
为什么协程能并发?
核心原理:协作式调度 + 异步I/O
# 协程的底层实现原理(简化版)
class Coroutine:
"""协程对象的内部结构"""
def __init__(self, gen):
self.gen = gen # 生成器对象
self.state = 'CREATED' # CREATED, RUNNING, SUSPENDED, FINISHED
self.stack = [] # 协程栈帧
self.result = None
def send(self, value):
"""恢复协程执行"""
self.state = 'RUNNING'
try:
# 将值发送到生成器,恢复执行
result = self.gen.send(value)
self.state = 'SUSPENDED'
return result
except StopIteration as e:
self.state = 'FINISHED'
self.result = e.value
raise
class EventLoop:
"""事件循环的核心机制"""
def __init__(self):
self.ready_queue = [] # 就绪队列
self.waiting_io = {} # 等待I/O的协程
self.selector = selectors.DefaultSelector() # I/O多路复用
def create_task(self, coro):
"""创建任务"""
task = Task(coro)
self.ready_queue.append(task)
return task
def run_forever(self):
"""事件循环主逻辑"""
while True:
# 1. 执行所有就绪的协程
while self.ready_queue:
task = self.ready_queue.popleft()
try:
# 执行协程直到下一个await点
result = task.coro.send(None)
# 如果协程await了一个I/O操作
if isinstance(result, IOWait):
# 注册到I/O多路复用器
self.selector.register(
result.fd,
result.events,
task
)
self.waiting_io[result.fd] = task
else:
# 协程主动yield,放回就绪队列
self.ready_queue.append(task)
except StopIteration:
# 协程执行完毕
task.set_done()
# 2. 等待I/O事件(非阻塞或短超时)
events = self.selector.select(timeout=0)
# 3. 处理I/O事件,唤醒等待的协程
for key, mask in events:
task = key.data
self.selector.unregister(key.fd)
del self.waiting_io[key.fd]
# 将协程放回就绪队列
self.ready_queue.append(task)
协程调度的时间线:
时间轴(单线程内):
|--C1执行--|--C2执行--|--C3执行--|--C1继续--|--C2继续--|
详细分解:
t=0ms: 协程C1开始执行,遇到 await read_socket()
- C1主动挂起(yield),保存状态
- 事件循环注册socket到epoll
- 切换到协程C2(仅修改栈指针,无系统调用!)
t=1ms: 协程C2执行,遇到 await sleep(1)
- C2主动挂起
- 事件循环设置定时器
- 切换到协程C3
t=2ms: 协程C3执行,遇到 await http_request()
- C3主动挂起
- 事件循环调用epoll_wait()检查I/O
t=10ms: C1的socket有数据到达
- epoll_wait()返回就绪事件
- 事件循环恢复C1执行
- C1读取数据,继续执行后续代码
t=1000ms: C2的定时器到期
- 事件循环恢复C2执行
关键点:
1. 协程切换完全在用户态,无系统调用
2. 切换仅需保存/恢复栈指针和程序计数器
3. 开销极小:约0.1-0.5微秒(线程切换约5-10微秒)
4. 单线程内调度,无需锁,无GIL竞争
6.4.2 为什么协程能处理大规模并发?
import asyncio
import time
async def handle_client(client_id):
"""模拟处理一个客户端请求"""
print(f"开始处理客户端 {client_id}")
# 模拟I/O等待
await asyncio.sleep(0.1)
print(f"完成处理客户端 {client_id}")
return f"结果{client_id}"
async def main():
"""同时处理10000个客户端"""
tasks = [handle_client(i) for i in range(10000)]
results = await asyncio.gather(*tasks)
# 运行
start = time.time()
asyncio.run(main())
print(f"耗时: {time.time() - start:.2f}秒")
# 输出约0.1秒,因为10000个协程并发等待I/O
# 内存占用分析:
# - 每个协程约2KB(栈帧)
# - 10000个协程约20MB
# - 如果用线程,每个约8MB(线程栈),10000个需要80GB!
为什么协程如此轻量?
线程 vs 协程的内存布局:
线程(由操作系统管理):
┌────────────────────────┐
│ Thread Control Block │ 约1KB
│ - TID │
│ - 调度信息 │
│ - 寄存器上下文 │
├────────────────────────┤
│ Thread Stack │ 默认8MB(Linux)
│ (预分配,防止溢出) │
└────────────────────────┘
总计: ~8MB/线程
协程(由程序管理):
┌────────────────────────┐
│ Coroutine Object │ 约200B
│ - 状态 │
│ - 栈指针 │
│ - 返回值 │
├────────────────────────┤
│ Stack Frame │ 按需增长,约2KB
│ (实际使用的栈空间) │
└────────────────────────┘
总计: ~2KB/协程
差异原因:
1. 协程栈按需分配,线程栈预分配
2. 协程在用户态管理,无内核开销
3. 协程共享同一线程栈的地址空间
6.5 三者对比总结:为什么能实现并发/并行的根本原因
┌──────────────────────────────────────────────────────────────────┐
│ 实现并发/并行的本质 │
└──────────────────────────────────────────────────────────────────┘
进程(Process):
├─ 并发原理: 操作系统时间分片 + 快速上下文切换
│ - 定时器中断强制切换
│ - 保存/恢复CPU寄存器和内存映射
│ - 调度算法选择下一个进程
│
└─ 并行原理: 多核CPU物理并行
- 每个核心运行独立进程
- 独立的地址空间,无共享冲突
- Python多进程绕过GIL,真并行
线程(Thread):
├─ 并发原理: 与进程相同的时间分片机制
│ - 线程是轻量级进程
│ - 共享地址空间,切换更快
│ - Python线程受GIL限制,无法CPU并行
│
└─ 并行潜力: 原生线程可以并行(C/Java等)
- Python线程因GIL无法CPU并行
- I/O操作释放GIL,可以I/O并发
协程(Coroutine):
├─ 并发原理: 用户态协作式调度
│ - 主动yield让出CPU
│ - 事件循环统一调度
│ - 无系统调用,切换极快
│
└─ 无并行能力: 运行在单线程内
- 无法利用多核
- 适合I/O密集,不适合CPU密集
- 需配合多进程实现CPU并行
6.5.1 深入到CPU指令级别的理解
单核CPU执行过程(纳秒级时间线):
t=0ns: 执行进程P1的指令: MOV eax, [0x1000]
t=2ns: 执行进程P1的指令: ADD eax, ebx
t=4ns: 执行进程P1的指令: MOV [0x1004], eax
...
t=10000000ns (10ms): 定时器中断触发!
├─ 硬件保存PC(程序计数器)到栈
├─ 跳转到中断处理程序
├─ 内核保存P1的所有寄存器
├─ 调度器选择进程P2
├─ 恢复P2的所有寄存器
└─ 跳转到P2的PC位置
t=10000002ns: 执行进程P2的指令: MOV ecx, [0x2000]
...
关键洞察:
1. CPU没有"同时执行"的魔法,只是切换得极快
2. 并发 = 快速轮换 + 人类感知延迟(>50ms才能感知)
3. 并行 = 真正的多个CPU核心同时工作
6.5.2 为什么协程比线程快?
上下文切换开销对比:
线程切换(内核态):
1. 用户态陷入内核态 (SYSCALL指令) ~100ns
2. 保存寄存器状态到线程控制块 ~50ns
3. 切换内存映射(如果是进程) ~500ns
4. 刷新TLB(Translation Lookaside Buffer) ~200ns
5. 调度器选择下一个线程 ~100ns
6. 恢复目标线程寄存器 ~50ns
7. 从内核态返回用户态 ~100ns
总计: ~5000-10000ns (5-10微秒)
协程切换(用户态):
1. 保存当前栈指针到协程对象 ~10ns
2. 更新协程状态标志 ~5ns
3. 从事件循环选择下一个协程 ~20ns
4. 恢复目标协程栈指针 ~10ns
5. 跳转到目标协程代码 ~5ns
总计: ~100-500ns (0.1-0.5微秒)
速度差异: 协程比线程快10-100倍!
6.6 实战验证:测量切换开销
import threading
import asyncio
import time
import os
# 1. 测量线程切换开销
def measure_thread_switch():
"""测量线程上下文切换的开销"""
switch_count = 1000000
def thread_func(barrier, counter):
for _ in range(switch_count):
# 强制线程切换
time.sleep(0) # 触发系统调用,让出CPU
counter[0] += 1
counter = [0]
barrier = threading.Barrier(2)
t1 = threading.Thread(target=thread_func, args=(barrier, counter))
t2 = threading.Thread(target=thread_func, args=(barrier, counter))
start = time.perf_counter()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.perf_counter()
total_switches = counter[0]
time_per_switch = (end - start) / total_switches * 1000000 # 微秒
print(f"线程切换: {time_per_switch:.2f} 微秒/次")
# 2. 测量协程切换开销
async def measure_coroutine_switch():
"""测量协程切换的开销"""
switch_count = 1000000
counter = 0
async def coro_func():
nonlocal counter
for _ in range(switch_count):
await asyncio.sleep(0) # 主动让出控制权
counter += 1
start = time.perf_counter()
await asyncio.gather(coro_func(), coro_func())
end = time.perf_counter()
time_per_switch = (end - start) / counter * 1000000 # 微秒
print(f"协程切换: {time_per_switch:.2f} 微秒/次")
# 3. 测量进程切换开销(间接测量)
def measure_process_switch():
"""测量进程切换的开销"""
# 通过vmstat或perf工具间接测量
import subprocess
# 在Linux上使用perf
if os.name == 'posix':
result = subprocess.run(
['perf', 'stat', '-e', 'context-switches', 'sleep', '1'],
capture_output=True,
text=True
)
print("进程切换统计:")
print(result.stderr)
# 运行测试
if __name__ == "__main__":
print("="*50)
print("上下文切换开销测量")
print("="*50)
measure_thread_switch()
asyncio.run(measure_coroutine_switch())
# 预期输出:
# 线程切换: 5-10 微秒/次
# 协程切换: 0.1-0.5 微秒/次
6.7 终极总结:选择的智慧
"""
并发/并行的本质与选择策略
"""
# 1. CPU密集型: 需要真并行
def cpu_intensive():
# 为什么用多进程?
# - 需要多核同时计算
# - 每个进程独立GIL
# - 真正的并行执行
from multiprocessing import Pool
with Pool(4) as p:
result = p.map(heavy_computation, data)
# 2. I/O密集型: 需要高并发
async def io_intensive():
# 为什么用协程?
# - I/O等待期间可以处理其他任务
# - 轻量级,可以创建数万个
# - 事件驱动,响应快
tasks = [fetch_url(url) for url in urls]
results = await asyncio.gather(*tasks)
# 3. 混合负载: 需要组合
def hybrid_workload():
# 进程池 + 协程
# - 每个进程处理一部分数据(并行)
# - 进程内用协程处理I/O(并发)
from concurrent.futures import ProcessPoolExecutor
async def process_partition(data):
async with aiohttp.ClientSession() as session:
tasks = [fetch_and_process(session, item) for item in data]
return await asyncio.gather(*tasks)
with ProcessPoolExecutor(4) as executor:
futures = [
executor.submit(asyncio.run, process_partition(partition))
for partition in data_partitions
]
记住核心原则:
- 并发的本质: 时间分片 + 快速切换,让多个任务轮流执行
- 并行的本质: 多个物理执行单元同时工作
- 进程: 操作系统调度,独立地址空间,可真并行
- 线程: 操作系统调度,共享地址空间,Python受GIL限制
- 协程: 用户态调度,极轻量级,适合I/O密集
- 选择依据: 任务类型(CPU/IO) + 规模 + 资源限制
一句话总结:
并发是软件设计,并行是硬件能力。进程、线程、协程提供了从硬件到软件的不同抽象层次,让我们能充分利用计算资源,实现高效的并发与并行处理。
七、实战场景与选择策略
决策树
任务类型?
│
├─ CPU密集型(计算为主)
│ │
│ ├─ 需要真正并行?
│ │ ├─ 是 → 使用多进程 (multiprocessing)
│ │ └─ 否 → 串行执行或优化算法
│ │
│ └─ 数据量大?
│ ├─ 是 → 考虑numpy/numba等优化库
│ └─ 否 → 多进程 + 进程池
│
└─ I/O密集型(等待为主)
│
├─ 并发数量?
│ ├─ 少(<100) → 多线程 (threading)
│ ├─ 中(100-1000) → 协程 (asyncio)
│ └─ 多(>1000) → 协程 (asyncio)
│
└─ 是否需要共享状态?
├─ 是 → 多线程 + 锁
└─ 否 → 协程(更简单)
实战场景示例
场景1:Web爬虫
import asyncio
import aiohttp
from bs4 import BeautifulSoup
import time
class AsyncWebCrawler:
"""异步网络爬虫"""
def __init__(self, max_concurrent=10):
self.max_concurrent = max_concurrent
self.semaphore = asyncio.Semaphore(max_concurrent)
self.session = None
async def fetch(self, url):
"""获取单个URL"""
async with self.semaphore:
try:
async with self.session.get(url, timeout=10) as response:
html = await response.text()
return url, html
except Exception as e:
print(f"Error fetching {url}: {e}")
return url, None
async def parse(self, url, html):
"""解析HTML"""
if not html:
return []
soup = BeautifulSoup(html, 'html.parser')
links = []
for link in soup.find_all('a'):
href = link.get('href')
if href:
links.append(href)
return links
async def crawl(self, start_urls, max_pages=100):
"""爬取网站"""
visited = set()
to_visit = set(start_urls)
results = []
async with aiohttp.ClientSession() as session:
self.session = session
while to_visit and len(visited) < max_pages:
# 批量获取当前待访问的URL
current_batch = list(to_visit)[:self.max_concurrent]
to_visit -= set(current_batch)
# 并发获取
fetch_tasks = [self.fetch(url) for url in current_batch]
pages = await asyncio.gather(*fetch_tasks)
# 解析并提取新链接
for url, html in pages:
if html:
visited.add(url)
links = await self.parse(url, html)
# 添加新发现的链接
new_links = set(links) - visited - to_visit
to_visit.update(new_links)
results.append((url, len(links)))
return results
# 使用示例
async def main():
crawler = AsyncWebCrawler(max_concurrent=20)
start_urls = ['https://example.com']
start = time.time()
results = await crawler.crawl(start_urls, max_pages=50)
elapsed = time.time() - start
print(f"爬取了 {len(results)} 个页面,耗时 {elapsed:.2f}秒")
print(f"平均速度: {len(results)/elapsed:.2f} 页/秒")
# asyncio.run(main())
# 选择理由:
# ✅ I/O密集型(网络请求)
# ✅ 高并发需求(可能上千个URL)
# ✅ 协程开销小,适合大量并发
# ✅ 单线程,不需要锁
场景2:数据处理流水线
from multiprocessing import Process, Queue, Pool
import time
import json
class DataPipeline:
"""数据处理流水线"""
@staticmethod
def extract(data_source):
"""提取数据(I/O密集)"""
with open(data_source, 'r') as f:
return json.load(f)
@staticmethod
def transform(data):
"""转换数据(CPU密集)"""
# 复杂的数据转换逻辑
result = []
for item in data:
# 模拟CPU密集型计算
processed = {
'id': item['id'],
'value': sum(i**2 for i in range(1000)),
'transformed': item['value'] * 2
}
result.append(processed)
return result
@staticmethod
def load(data, target):
"""加载数据(I/O密集)"""
with open(target, 'w') as f:
json.dump(data, f)
def process_batch(self, batch_data, output_queue):
"""处理一批数据"""
# 转换(CPU密集型)使用当前进程
transformed = self.transform(batch_data)
output_queue.put(transformed)
def run_pipeline(self, input_file, output_file, num_workers=4):
"""运行流水线"""
# 1. 提取数据
print("提取数据...")
all_data = self.extract(input_file)
# 2. 分批处理(多进程)
print(f"使用 {num_workers} 个进程转换数据...")
batch_size = len(all_data) // num_workers
batches = [
all_data[i:i+batch_size]
for i in range(0, len(all_data), batch_size)
]
# 使用进程池
with Pool(processes=num_workers) as pool:
results = pool.map(self.transform, batches)
# 3. 合并结果
final_data = []
for batch_result in results:
final_data.extend(batch_result)
# 4. 加载数据
print("加载数据...")
self.load(final_data, output_file)
print(f"完成!处理了 {len(final_data)} 条记录")
# 选择理由:
# ✅ Extract/Load是I/O密集型,可以串行或少量并发
# ✅ Transform是CPU密集型,使用多进程并行
# ✅ 混合型任务,分而治之
场景3:实时数据处理
import asyncio
import aioredis
from typing import List, Dict
class RealtimeDataProcessor:
"""实时数据处理系统"""
def __init__(self):
self.redis = None
self.handlers = []
async def connect(self):
"""连接到Redis"""
self.redis = await aioredis.create_redis_pool('redis://localhost')
async def subscribe_channel(self, channel: str):
"""订阅频道"""
channels = await self.redis.subscribe(channel)
return channels[0]
async def process_message(self, message: Dict):
"""处理单条消息"""
# 快速处理,不阻塞
data = message.get('data')
# 异步保存到数据库
await self.save_to_db(data)
# 异步推送到其他服务
await self.push_to_service(data)
# 触发回调
for handler in self.handlers:
await handler(data)
async def save_to_db(self, data):
"""异步保存到数据库"""
# 模拟数据库操作
await asyncio.sleep(0.01)
async def push_to_service(self, data):
"""推送到其他服务"""
# 模拟API调用
await asyncio.sleep(0.02)
async def run(self, channels: List[str]):
"""运行处理器"""
await self.connect()
# 订阅多个频道
channel_objects = await asyncio.gather(*[
self.subscribe_channel(ch) for ch in channels
])
# 并发处理所有频道的消息
async def listen_channel(channel):
while True:
message = await channel.get()
if message:
# 不等待处理完成,继续接收下一条
asyncio.create_task(self.process_message(message))
# 并发监听所有频道
await asyncio.gather(*[
listen_channel(ch) for ch in channel_objects
])
# 选择理由:
# ✅ 高并发消息处理
# ✅ I/O密集型(网络、数据库)
# ✅ 需要快速响应
# ✅ 协程最适合
场景4:图像批量处理
from multiprocessing import Pool, cpu_count
from PIL import Image
import os
class ImageProcessor:
"""图像批量处理器"""
@staticmethod
def process_single_image(args):
"""处理单张图片(CPU密集型)"""
input_path, output_path, operations = args
try:
# 打开图片
img = Image.open(input_path)
# 应用操作
for op in operations:
if op['type'] == 'resize':
img = img.resize(op['size'])
elif op['type'] == 'rotate':
img = img.rotate(op['angle'])
elif op['type'] == 'filter':
# 应用滤镜(CPU密集)
img = img.filter(op['filter'])
# 保存
img.save(output_path)
return True, input_path
except Exception as e:
return False, f"Error processing {input_path}: {e}"
def process_batch(self, image_paths: List[str], operations: List[Dict],
output_dir: str, num_workers: int = None):
"""批量处理图片"""
if num_workers is None:
num_workers = cpu_count()
# 准备参数
tasks = []
for path in image_paths:
filename = os.path.basename(path)
output_path = os.path.join(output_dir, filename)
tasks.append((path, output_path, operations))
# 使用进程池并行处理
print(f"使用 {num_workers} 个进程处理 {len(tasks)} 张图片...")
with Pool(processes=num_workers) as pool:
results = pool.map(self.process_single_image, tasks)
# 统计结果
success = sum(1 for r in results if r[0])
failed = len(results) - success
print(f"成功: {success}, 失败: {failed}")
return results
# 使用示例
processor = ImageProcessor()
operations = [
{'type': 'resize', 'size': (800, 600)},
{'type': 'rotate', 'angle': 90},
]
image_paths = ['img1.jpg', 'img2.jpg', 'img3.jpg'] # 假设有很多图片
processor.process_batch(image_paths, operations, 'output/')
# 选择理由:
# ✅ CPU密集型(图像处理)
# ✅ 可以完全并行
# ✅ 使用多进程充分利用多核
# ✅ 每个任务独立,无需通信
八、最佳实践
1. 任务类型识别
import time
import psutil
import os
class TaskProfiler:
"""任务性能分析器"""
@staticmethod
def profile_task(func, *args, **kwargs):
"""分析任务类型"""
process = psutil.Process(os.getpid())
# 记录初始状态
cpu_before = process.cpu_percent(interval=0.1)
io_before = process.io_counters()
# 执行任务
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
# 记录最终状态
cpu_after = process.cpu_percent(interval=0.1)
io_after = process.io_counters()
# 计算I/O量
io_read = io_after.read_bytes - io_before.read_bytes
io_write = io_after.write_bytes - io_before.write_bytes
# 判断类型
avg_cpu = (cpu_before + cpu_after) / 2
total_io = io_read + io_write
if avg_cpu > 70:
task_type = "CPU密集型"
recommendation = "使用多进程(multiprocessing)"
elif total_io > 1024 * 1024: # > 1MB
task_type = "I/O密集型"
recommendation = "使用协程(asyncio)或多线程"
else:
task_type = "混合型"
recommendation = "根据主要瓶颈选择"
print(f"\n任务分析结果:")
print(f"执行时间: {elapsed:.2f}秒")
print(f"CPU使用: {avg_cpu:.1f}%")
print(f"I/O读取: {io_read / 1024:.2f} KB")
print(f"I/O写入: {io_write / 1024:.2f} KB")
print(f"任务类型: {task_type}")
print(f"建议方案: {recommendation}")
return result
# 使用示例
def my_task():
# 你的任务代码
result = sum(i**2 for i in range(1000000))
return result
TaskProfiler.profile_task(my_task)
2. 混合任务处理策略
import asyncio
from concurrent.futures import ProcessPoolExecutor
import aiohttp
class HybridTaskExecutor:
"""混合任务执行器"""
def __init__(self, max_processes=4):
self.process_executor = ProcessPoolExecutor(max_workers=max_processes)
async def execute_hybrid_pipeline(self, data_sources):
"""执行混合流水线"""
results = []
# 步骤1:并发获取数据(I/O密集)
async with aiohttp.ClientSession() as session:
fetch_tasks = [
self.fetch_data(session, source)
for source in data_sources
]
raw_data = await asyncio.gather(*fetch_tasks)
# 步骤2:并行处理数据(CPU密集)
loop = asyncio.get_event_loop()
process_tasks = [
loop.run_in_executor(self.process_executor, self.process_data, data)
for data in raw_data
]
processed_data = await asyncio.gather(*process_tasks)
# 步骤3:并发保存结果(I/O密集)
save_tasks = [
self.save_result(result)
for result in processed_data
]
await asyncio.gather(*save_tasks)
return processed_data
async def fetch_data(self, session, source):
"""获取数据(I/O密集 - 协程)"""
async with session.get(source) as response:
return await response.json()
@staticmethod
def process_data(data):
"""处理数据(CPU密集 - 进程)"""
# 复杂计算
return {
'processed': sum(item**2 for item in range(100000)),
'original': data
}
async def save_result(self, result):
"""保存结果(I/O密集 - 协程)"""
await asyncio.sleep(0.1) # 模拟I/O
return True
def __del__(self):
self.process_executor.shutdown()
# 使用
async def main():
executor = HybridTaskExecutor(max_processes=4)
sources = ['http://api1.com/data', 'http://api2.com/data']
results = await executor.execute_hybrid_pipeline(sources)
# asyncio.run(main())
3. 性能监控与优化
import time
import functools
from typing import Callable, Any
import asyncio
def performance_monitor(task_type: str = 'unknown'):
"""性能监控装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def async_wrapper(*args, **kwargs) -> Any:
start = time.time()
try:
result = await func(*args, **kwargs)
elapsed = time.time() - start
print(f"[{task_type}] {func.__name__} 耗时: {elapsed:.3f}秒")
return result
except Exception as e:
elapsed = time.time() - start
print(f"[{task_type}] {func.__name__} 失败: {e}, 耗时: {elapsed:.3f}秒")
raise
@functools.wraps(func)
def sync_wrapper(*args, **kwargs) -> Any:
start = time.time()
try:
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"[{task_type}] {func.__name__} 耗时: {elapsed:.3f}秒")
return result
except Exception as e:
elapsed = time.time() - start
print(f"[{task_type}] {func.__name__} 失败: {e}, 耗时: {elapsed:.3f}秒")
raise
return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
return decorator
# 使用示例
@performance_monitor(task_type='I/O')
async def fetch_data(url):
await asyncio.sleep(1)
return "data"
@performance_monitor(task_type='CPU')
def compute_result(n):
return sum(i**2 for i in range(n))
4. 常见陷阱与解决方案
# 陷阱1:在异步函数中使用同步I/O
# ❌ 错误示例
async def bad_async():
import time
time.sleep(1) # 阻塞整个事件循环!
return "done"
# ✅ 正确示例
async def good_async():
await asyncio.sleep(1) # 非阻塞
return "done"
# 陷阱2:在多线程中修改共享状态不加锁
# ❌ 错误示例
counter = 0
def bad_thread_func():
global counter
for _ in range(100000):
counter += 1 # 竞态条件!
# ✅ 正确示例
from threading import Lock
counter = 0
lock = Lock()
def good_thread_func():
global counter
for _ in range(100000):
with lock:
counter += 1
# 陷阱3:创建过多进程
# ❌ 错误示例
from multiprocessing import Process
processes = []
for i in range(1000): # 太多进程!
p = Process(target=some_func)
processes.append(p)
p.start()
# ✅ 正确示例
from multiprocessing import Pool
with Pool(processes=cpu_count()) as pool:
results = pool.map(some_func, range(1000))
# 陷阱4:忘记join进程/线程
# ❌ 错误示例
import threading
t = threading.Thread(target=some_func)
t.start()
# 主程序可能在线程完成前就退出
# ✅ 正确示例
t = threading.Thread(target=some_func)
t.start()
t.join() # 等待线程完成
九、总结
快速决策表
| 场景 | CPU使用 | I/O等待 | 并发数 | 推荐方案 | 原因 |
|---|---|---|---|---|---|
| Web爬虫 | 低 | 高 | 高(>1000) | 协程 | I/O密集,高并发 |
| 数据分析 | 高 | 低 | 中 | 多进程 | CPU密集,需要并行 |
| API服务 | 低 | 高 | 高 | 协程 | I/O密集,快速响应 |
| 图像处理 | 高 | 中 | 中 | 多进程 | CPU密集,可并行 |
| 文件处理 | 低 | 高 | 低 | 多线程 | I/O密集,简单 |
| 机器学习训练 | 高 | 低 | - | 多进程+GPU | CPU密集,大计算量 |
| 实时消息 | 低 | 高 | 极高 | 协程 | I/O密集,超高并发 |
记忆口诀
CPU密集用进程,多核并行真给力
I/O密集用协程,千万并发不是梦
线程适合小并发,共享内存很方便
混合任务需组合,流水线式最高效
GIL影响要记牢,线程计算不给力
进程隔离更安全,通信代价要考虑
协程轻量启动快,单线程内来调度
选对方案事半功,性能提升看得见
最后建议
- 先分析后选择:使用性能分析工具确定瓶颈
- 从简单开始:能串行就串行,必要时才并发
- 测量再优化:不要过早优化,用数据说话
- 考虑维护性:复杂的并发代码难以调试
- 关注资源:并发不是越多越好,要考虑系统资源
记住:没有银弹,只有最适合的方案!