Python 并发编程实战
目录
学习目标
- 综合运用多线程、多进程和异步编程
- 学会选择合适的并发模型
- 掌握性能优化的基本方法
- 理解并发编程中的常见问题
1. 并发模型选择
1.1 决策树
任务类型?
├── CPU 密集型(计算、数据处理)
│ └── 选择:多进程 (multiprocessing)
├── IO 密集型(网络、文件)
│ └── 选择:异步 (asyncio) 或多线程
└── 混合类型
└── 选择:多进程 + 异步,或线程池1.2 性能对比示例
import time
import threading
import multiprocessing
import asyncio
def cpu_task(n=5000000):
"""CPU 密集型"""
count = 0
for i in range(n):
count += i * i
return count
def io_task(delay=1):
"""IO 密集型"""
time.sleep(delay)
return "done"
async def async_io_task(delay=1):
"""异步 IO"""
await asyncio.sleep(delay)
return "done"
# 测试函数
def benchmark():
print("=== CPU 密集型 (4 任务) ===")
# 串行
start = time.time()
[cpu_task() for _ in range(4)]
print(f"串行: {time.time() - start:.2f}s")
# 多线程(GIL 限制,不会更快)
start = time.time()
threads = [threading.Thread(target=cpu_task) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(f"多线程: {time.time() - start:.2f}s")
# 多进程
start = time.time()
with multiprocessing.Pool(4) as pool:
pool.map(cpu_task, [5000000] * 4)
print(f"多进程: {time.time() - start:.2f}s")
print("\n=== IO 密集型 (10 任务) ===")
# 串行
start = time.time()
[io_task() for _ in range(10)]
print(f"串行: {time.time() - start:.2f}s")
# 多线程
start = time.time()
threads = [threading.Thread(target=io_task) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(f"多线程: {time.time() - start:.2f}s")
# 异步
async def async_main():
tasks = [async_io_task() for _ in range(10)]
await asyncio.gather(*tasks)
start = time.time()
asyncio.run(async_main())
print(f"异步: {time.time() - start:.2f}s")
if __name__ == '__main__':
benchmark()2. 实战案例
2.1 并发下载器
import asyncio
import aiohttp
import time
async def download(url, session):
"""下载单个 URL"""
try:
async with session.get(url, timeout=10) as response:
content = await response.read()
return url, len(content)
except Exception as e:
return url, f"错误: {e}"
async def download_all(urls, max_concurrent=5):
"""并发下载多个 URL"""
# 使用信号量限制并发数
semaphore = asyncio.Semaphore(max_concurrent)
async def bounded_download(url, session):
async with semaphore:
return await download(url, session)
async with aiohttp.ClientSession() as session:
tasks = [bounded_download(url, session) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
# 使用示例
urls = [
"https://www.python.org",
"https://www.github.com",
"https://www.google.com",
] * 3
async def main():
start = time.time()
results = await download_all(urls, max_concurrent=5)
for url, result in results:
print(f"{url}: {result}")
print(f"\n总耗时: {time.time() - start:.2f}s")
# asyncio.run(main())2.2 混合并发模型
import asyncio
import multiprocessing
from concurrent.futures import ProcessPoolExecutor
async def cpu_bound_task(data):
"""CPU 密集型任务"""
return sum(x * x for x in data)
def cpu_bound_sync(data):
"""同步版本用于进程池"""
return sum(x * x for x in data)
async def io_bound_task(url):
"""IO 密集型任务"""
await asyncio.sleep(0.1) # 模拟网络请求
return f"数据: {url}"
async def mixed_workflow():
"""混合工作流"""
# IO 密集型:获取多个数据源
urls = [f"url_{i}" for i in range(10)]
io_tasks = [io_bound_task(url) for url in urls]
raw_data = await asyncio.gather(*io_tasks)
# CPU 密集型:处理数据(使用进程池)
data_chunks = [[i] * 100000 for i in range(4)]
loop = asyncio.get_event_loop()
with ProcessPoolExecutor() as pool:
# 在线程池中运行 CPU 密集型任务
cpu_tasks = [
loop.run_in_executor(pool, cpu_bound_sync, chunk)
for chunk in data_chunks
]
processed = await asyncio.gather(*cpu_tasks)
return raw_data, processed
# asyncio.run(mixed_workflow())2.3 生产者-消费者模式
import asyncio
async def producer(queue, n):
"""生产者"""
for i in range(n):
item = f"产品-{i}"
await queue.put(item)
print(f"生产: {item}")
await asyncio.sleep(0.1)
# 发送结束信号
for _ in range(3): # 3 个消费者
await queue.put(None)
async def consumer(queue, name):
"""消费者"""
while True:
item = await queue.get()
if item is None:
break
# 模拟处理
await asyncio.sleep(0.2)
print(f"消费者 {name} 处理: {item}")
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=10)
# 创建任务
producers = [asyncio.create_task(producer(queue, 10))]
consumers = [
asyncio.create_task(consumer(queue, f"C{i}"))
for i in range(3)
]
# 等待完成
await asyncio.gather(*producers)
await asyncio.gather(*consumers)
print("全部完成")
asyncio.run(main())3. 常见问题和解决方案
3.1 死锁
import threading
# 错误示例:嵌套锁可能导致死锁
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread1():
with lock_a:
print("线程1获取锁A")
with lock_b: # 可能死锁
print("线程1获取锁B")
def thread2():
with lock_b:
print("线程2获取锁B")
with lock_a: # 可能死锁
print("线程2获取锁A")
# 解决方案1:总是按相同顺序获取锁
# 解决方案2:使用 RLock(可重入锁)
# 解决方案3:使用 timeout
def safe_thread1():
if lock_a.acquire(timeout=5):
try:
print("线程1获取锁A")
if lock_b.acquire(timeout=5):
try:
print("线程1获取锁B")
finally:
lock_b.release()
finally:
lock_a.release()3.2 竞态条件
import threading
# 错误:竞态条件
counter = 0
def increment():
global counter
# 读取 -> 修改 -> 写入 不是原子操作
current = counter
counter = current + 1
# 解决方案:使用锁或原子操作
lock = threading.Lock()
def safe_increment():
global counter
with lock:
counter += 1
# 或使用 queue.Queue(线程安全)
# 或使用 multiprocessing.Value(进程安全)3.3 资源泄漏
import asyncio
# 错误:未取消任务可能导致资源泄漏
async def leaky():
task = asyncio.create_task(asyncio.sleep(10))
# 如果这里发生异常,任务可能仍在运行
await asyncio.sleep(1)
# task 仍在后台运行!
# 正确:使用 try/finally 或 async with
async def clean():
task = asyncio.create_task(asyncio.sleep(10))
try:
await asyncio.sleep(1)
finally:
task.cancel()
try:
await task
except asyncio.CancelledError:
pass4. 性能优化技巧
4.1 限制并发数
import asyncio
async def limited_task(sem, i):
async with sem:
await asyncio.sleep(1)
return i
async def main():
# 最多 5 个并发
sem = asyncio.Semaphore(5)
tasks = [limited_task(sem, i) for i in range(20)]
results = await asyncio.gather(*tasks)
print(f"完成 {len(results)} 个任务")
asyncio.run(main())4.2 批量处理
import asyncio
async def process_batch(items):
"""批量处理"""
tasks = [process_item(item) for item in items]
return await asyncio.gather(*tasks)
async def process_all(items, batch_size=10):
"""分批处理大量数据"""
results = []
for i in range(0, len(items), batch_size):
batch = items[i:i + batch_size]
batch_results = await process_batch(batch)
results.extend(batch_results)
return results
async def process_item(item):
await asyncio.sleep(0.1)
return item * 2
# 使用
# asyncio.run(process_all(range(100)))本节小结
- 模型选择:CPU 密集用多进程,IO 密集用异步/多线程
- 混合模型:可以结合多进程和异步获得最佳性能
- 常见问题:死锁、竞态条件、资源泄漏
- 最佳实践:限制并发、使用超时、正确清理资源
- 调试工具:
asyncio.run()调试模式、日志记录
练习
- 实现一个并发图片下载器,支持限速和断点续传
- 编写一个混合并发的数据处理管道:读取(IO)-> 处理(CPU)-> 写入(IO)
- 实现一个线程/进程安全的连接池
- 使用 asyncio 和 aiohttp 实现一个简单的并发 Web 爬虫