并发编程是现代软件开发中提高程序性能的重要手段。Python提供了多种并发编程方式,包括多线程、多进程和异步IO。
基本概念
1 并发 vs 并行
-
并发(Concurrency): 多个任务交替执行,看起来像是同时运行
-
并行(Parallelism): 多个任务真正同时执行,需要多核CPU支持
2 Python的并发模型
-
I/O密集型任务: 适合使用多线程或异步IO
-
CPU密集型任务: 适合使用多进程
多线程编程
Python通过threading
模块提供线程支持,但由于GIL(全局解释器锁)的存在,多线程不适合CPU密集型任务。
1 基本使用
import threading
import time
def task(name):
print(f"任务 {name} 开始")
time.sleep(2) # 模拟I/O操作
print(f"任务 {name} 完成")
# 创建线程
threads = []
for i in range(3):
t = threading.Thread(target=task, args=(f"Thread-{i}",))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("所有任务完成")
2 线程同步
# 使用锁
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock: # 自动获取和释放锁
counter += 1
threads = []
for _ in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"最终计数器值: {counter}") # 应该是500000
# 使用信号量
semaphore = threading.Semaphore(3) # 最多3个线程同时访问
def limited_resource(user):
with semaphore:
print(f"{user} 正在使用资源")
time.sleep(1)
print(f"{user} 释放资源")
for i in range(10):
threading.Thread(target=limited_resource, args=(f"User-{i}",)).start()
3 线程间通信
# 使用队列
import queue
def producer(q):
for i in range(5):
print(f"生产物品 {i}")
q.put(i)
time.sleep(0.5)
q.put(None) # 结束信号
def consumer(q):
while True:
item = q.get()
if item is None:
break
print(f"消费物品 {item}")
time.sleep(1)
q = queue.Queue()
threading.Thread(target=producer, args=(q,)).start()
threading.Thread(target=consumer, args=(q,)).start()
多进程编程
多进程可以绕过GIL限制,适合CPU密集型任务,但进程间通信开销较大。
1 基本使用
from multiprocessing import Process
import os
def cpu_bound_task(n):
print(f"进程 {os.getpid()} 计算 {n} 的平方")
return n * n
if __name__ == '__main__':
processes = []
for i in range(4):
p = Process(target=cpu_bound_task, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
2 进程池
from multiprocessing import Pool
def square(x):
return x * x
if __name__ == '__main__':
with Pool(4) as pool: # 4个工作进程
# map方法
results = pool.map(square, range(10))
print(results) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# apply_async方法
result = pool.apply_async(square, (10,))
print(result.get()) # 100
3 进程间通信
from multiprocessing import Process, Queue
def worker(q):
while True:
item = q.get()
if item is None:
break
print(f"处理: {item}")
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
for i in range(5):
q.put(i)
q.put(None) # 结束信号
p.join()
异步IO(asyncio)
asyncio是Python3.4引入的标准库,适合I/O密集型任务,使用单线程实现高并发。
1 基本概念
-
协程(Coroutine): 使用
async def
定义的函数 -
事件循环(Event Loop): 协程的调度器
-
Future/Task: 表示异步操作的结果
2 基本使用
import asyncio async def fetch_data(url): print(f"开始获取 {url}") await asyncio.sleep(2) # 模拟I/O操作 print(f"完成获取 {url}") return f"{url} 的数据" async def main(): # 顺序执行 result1 = await fetch_data("url1") result2 = await fetch_data("url2") print(result1, result2) # 并发执行 task1 = asyncio.create_task(fetch_data("url3")) task2 = asyncio.create_task(fetch_data("url4")) await task1 await task2 # 使用gather results = await asyncio.gather( fetch_data("url5"), fetch_data("url6"), fetch_data("url7") ) print(results) asyncio.run(main())
3 高级特性
# 超时控制
async def slow_operation():
await asyncio.sleep(5)
return "完成"
async def main():
try:
result = await asyncio.wait_for(slow_operation(), timeout=3.0)
except asyncio.TimeoutError:
print("操作超时")
# 事件循环控制
async def periodic_task():
while True:
print("执行周期性任务")
await asyncio.sleep(1)
async def main():
task = asyncio.create_task(periodic_task())
await asyncio.sleep(5)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("任务已取消")
并发编程选择指南
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
多线程 | I/O密集型,GUI应用 | 轻量级,共享内存方便 | 受GIL限制,不适合CPU密集型 |
多进程 | CPU密集型任务 | 绕过GIL,利用多核 | 内存开销大,进程间通信复杂 |
异步IO | 高并发I/O操作,网络应用 | 高效,单线程高并发 | 需要特殊库支持,学习曲线陡峭 |
实际应用示例
1 并发下载器
import aiohttp
import asyncio
async def download(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
content = await response.read()
print(f"下载 {url} 完成,长度: {len(content)}")
return content
async def main():
urls = [
'https://www.python.org',
'https://www.google.com',
'https://www.github.com'
]
tasks = [download(url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
2 并行计算
from multiprocessing import Pool
import math
def is_prime(n):
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
if __name__ == '__main__':
numbers = range(1000000, 1000100)
with Pool(4) as pool:
results = pool.map(is_prime, numbers)
primes = [n for n, prime in zip(numbers, results) if prime]
print(f"找到 {len(primes)} 个质数")
常见问题
-
GIL限制:
-
使用多进程代替多线程处理CPU密集型任务
-
使用C扩展释放GIL
-
-
死锁问题:
-
按固定顺序获取锁
-
使用带超时的锁
-
-
资源竞争:
-
使用线程安全的数据结构
-
尽量减少共享状态
-
-
协程阻塞:
-
避免在协程中使用阻塞I/O
-
使用专门的异步库(aiohttp, asyncpg等)
-
通过合理选择并发模型并正确实现,可以显著提高Python程序的性能,特别是在处理I/O密集型或CPU密集型任务时。