文章9:并发编程与性能优化
目标
掌握并发编程的核心概念与工具,编写高效、可靠的Python程序。
一、多线程(Thread)与多进程(Process)的区别
1. 核心差异
特性 | 多线程(Thread) | 多进程(Process) |
---|---|---|
资源共享 | 共享进程内存空间(受GIL限制) | 独立内存空间,不共享数据 |
适用场景 | I/O密集型任务(如网络请求、文件读写) | CPU密集型任务(如计算、图像处理) |
性能 | 受GIL限制,无法真正并行 | 利用多核CPU,实现并行 |
资源消耗 | 轻量级(创建/切换开销小) | 重量级(内存占用大,进程间通信复杂) |
稳定性 | 一个线程崩溃可能影响整个进程 | 进程间隔离,崩溃不影响其他进程 |
2. 全局解释器锁(GIL)的影响
- GIL:CPython中,同一时间仅允许一个线程执行Python字节码。
- 影响:
- 多线程在CPU密集型任务中无法提升性能(如循环计算)。
- 多线程适合I/O操作,因等待I/O时会释放GIL。
3. 示例对比
scss
# CPU密集型任务:计算斐波那契数列
import time
import threading
import multiprocessing
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
# 多线程(受GIL限制,性能差)
def thread_task():
fib(35)
start = time.time()
threads = []
for _ in range(4):
t = threading.Thread(target=thread_task)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Thread time: {time.time() - start:.2f}s") # 约10秒(甚至更慢)
# 多进程(利用多核,性能提升)
def process_task():
fib(35)
start = time.time()
processes = []
for _ in range(4):
p = multiprocessing.Process(target=process_task)
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"Process time: {time.time() - start:.2f}s") # 约2.5秒
二、使用concurrent.futures
简化并发任务
1. 线程池与进程池
通过ThreadPoolExecutor
和ProcessPoolExecutor
实现高效任务调度。
2. 示例:下载多个网页
python
import concurrent.futures
import requests
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3"
]
def fetch_url(url):
response = requests.get(url)
return response.url, response.elapsed.total_seconds()
# 线程池(适合I/O密集型)
with concurrent.futures.ThreadPoolExecutor() as executor:
results = list(executor.map(fetch_url, urls))
print("Thread Pool Results:")
for url, time_taken in results:
print(f"{url} took {time_taken:.2f}s")
# 进程池(适合CPU密集型)
with concurrent.futures.ProcessPoolExecutor() as executor:
results = list(executor.map(fetch_url, urls)) # 注意:进程间通信开销可能更大
print("\nProcess Pool Results:")
for url, time_taken in results:
print(f"{url} took {time_taken:.2f}s")
3. 选择策略
- 线程池:I/O操作(如网络请求、文件读写)。
- 进程池:CPU计算(如数据处理、模型训练)。
三、Asyncio异步编程模型
1. 异步IO的核心概念
- 事件循环:管理所有协程的执行。
- 协程(Coroutine) :轻量级函数,通过
async def
定义,用await
暂停/恢复。 - 非阻塞:在等待I/O时,切换到其他任务,提升并发能力。
2. 示例:异步下载网页
python
import asyncio
import aiohttp
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3"
]
async def fetch_url(session, url):
async with session.get(url) as response:
return url, response.elapsed.total_seconds()
async def main():
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
tasks.append(fetch_url(session, url))
results = await asyncio.gather(*tasks)
for url, time_taken in results:
print(f"{url} took {time_taken:.2f}s")
asyncio.run(main())
3. 优势场景
- 高并发I/O:如Web服务器、实时数据处理。
- 单线程高效:避免GIL限制,减少线程切换开销。
四、性能分析工具
1. cProfile
:分析代码性能瓶颈
python
import cProfile
def compute_heavy():
return sum(i**2 for i in range(1000000))
cProfile.run('compute_heavy()')
输出示例:
bash
2 function calls in 0.002 seconds
2. timeit
:精确测量代码片段
python
import timeit
print(timeit.timeit('sum(range(1000))', number=1000))
# 输出:例如:0.007秒
3. 优化步骤
- 识别瓶颈 :用
cProfile
定位耗时函数。 - 替换算法/数据结构:如用NumPy替代纯Python循环。
- 并行化:将CPU任务转为多进程,I/O任务用异步或线程池。
五、综合案例:Web爬虫优化
1. 同步版(低效)
python
import requests
urls = ["https://example.com/page" + str(i) for i in range(10)]
def sync_crawl():
for url in urls:
response = requests.get(url)
print(f"Downloaded {url} in {response.elapsed.total_seconds():.2f}s")
sync_crawl()
2. 异步版(高效)
python
import asyncio
import aiohttp
async def async_crawl():
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
tasks.append(session.get(url))
responses = await asyncio.gather(*tasks)
for resp in responses:
print(f"Downloaded {resp.url} in {resp.elapsed.total_seconds():.2f}s")
asyncio.run(async_crawl())
总结
通过本文,你已掌握:
- 多线程/多进程:选择合适场景,绕过GIL限制。
- concurrent.futures:高效管理线程/进程池。
- Asyncio:用异步IO处理高并发I/O任务。
- 性能分析工具:定位瓶颈,针对性优化。
根据任务类型选择方案:
- CPU密集 → 多进程。
- I/O密集 → 多线程或异步IO。
- 高并发网络 → Asyncio。
现在,你可以用这些工具优化你的Python程序,提升性能与可靠性!