在 Python 开发中,并发编程是一个绕不开的话题。无论是处理高并发的 Web 请求,还是批量处理数据,掌握多线程都是进阶必经之路。
本文将从基础原理 、实战代码 、并发模型对比 以及面试高分话术四个维度,带你彻底搞懂 Python 并发。
核心概念:GIL 与线程模型
在深入代码之前,必须先理解 Python 的"紧箍咒"------GIL。
什么是 GIL?
GIL 全称是全局解释器锁。在 CPython 解释器中,同一时刻只能有一个线程在 CPU 上执行字节码。
GIL 的影响
- CPU 密集型任务 (如复杂计算、图像处理):多线程无效 ,甚至因为线程切换导致更慢。
- 解决方案 :使用多进程 (
multiprocessing)。
- 解决方案 :使用多进程 (
- I/O 密集型任务 (如文件读写、网络请求、数据库操作):多线程非常有效 。因为在等待 I/O 时,GIL 会释放,允许其他线程运行。
- 解决方案 :使用多线程 (
threading)。
- 解决方案 :使用多线程 (
️实战:如何使用 ThreadPoolExecutor
在 Python 3 中,我们不再推荐直接使用 threading.Thread,而是使用更高级的 concurrent.futures 模块,特别是 ThreadPoolExecutor。
场景一:批量处理(使用 map)
如果你有一堆数据需要并行处理,且不关心谁先做完,只在乎结果的顺序,map 是最简洁的选择。
python
import time
from concurrent.futures import ThreadPoolExecutor
def process_file(filename):
print(f"正在处理: {filename}")
time.sleep(1) # 模拟耗时 I/O
return f"{filename}_结果"
file_list = [f"file_{i}.txt" for i in range(5)]
# max_workers=3 表示同时最多 3 个线程
with ThreadPoolExecutor(max_workers=3) as executor:
# map 会按 file_list 的顺序返回结果
results = executor.map(process_file, file_list)
for res in results:
print(res)
场景二:精细控制(使用 submit)
如果你需要传递多个参数,或者需要"谁先做完先处理谁",甚至需要单独捕获异常,请使用 submit。
python
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def task(n):
time.sleep(n) # 模拟耗时不同
return f"任务{n}完成"
with ThreadPoolExecutor(max_workers=3) as executor:
# 1. 提交任务,拿到 Future 对象
futures = [executor.submit(task, i) for i in [3, 1, 2]]
# 2. as_completed 会在任务完成时立即产出,不按输入顺序
for future in as_completed(futures):
try:
print(future.result())
except Exception as e:
print(f"出错了: {e}")
# 输出顺序:任务1 -> 任务2 -> 任务3 (按完成时间排序)
进阶:多线程同步与顺序控制
面试中常考:如何让 5 个线程按顺序输出 1, 2, 3, 4, 5...?
这需要用到条件变量 。我们不能让线程乱跑,必须给它们定好规矩:只有轮到自己的数字时才能打印,否则就等待。
代码实现:Condition 控制流
python
import threading
from concurrent.futures import ThreadPoolExecutor
current_num = 1
max_num = 16
# 条件变量,自带一把锁
condition = threading.Condition()
def worker(thread_id):
global current_num
while True:
with condition: # 获取锁
# 只要没结束,且当前数字不是该我打印的,我就等待
# thread_id % 5 决定了该线程负责哪些数字 (1, 6, 11... 对应余数 1)
while current_num <= max_num and current_num % 5 != (thread_id % 5):
condition.wait() # 释放锁,挂起自己
if current_num <= max_num:
print(current_num, end=' ')
current_num += 1
condition.notify_all() # 唤醒所有人,让大家抢锁检查
else:
condition.notify_all() # 结束前唤醒,防止死锁
return
with ThreadPoolExecutor(max_workers=5) as executor:
for i in range(1, 6):
executor.submit(worker, i)
面试高分话术:Python vs Java Spring
如果面试官问:"你在 Python 中用多线程,那在 Java Spring 中是怎么做的?有什么区别?"
在 Python 中,我主要使用 concurrent.futures.ThreadPoolExecutor 来处理 I/O 密集型任务,通过 map 或 submit 提交任务。
在 Java Spring 中,这个思想是相通的,但实现更加优雅:
- 声明式异步:Spring 提供了 @Async 注解,配合 ThreadPoolTaskExecutor,可以零侵入地实现异步调用,这比 Python 的手动提交更简洁。
- 无 GIL 限制:Java 没有 GIL,线程池可以真正利用多核 CPU 进行并行计算,这在处理 CPU 密集型任务时比 Python 多线程更有优势。
- 强大的编排:Java 的 CompletableFuture 提供了非常强大的链式异步编程能力,比 Python 原生的 Future 功能更丰富,适合处理复杂的微服务调用编排。