在Python里做数据处理,如果任务主要是读取文件、清洗字段、做统计计算,往往会发现程序只用到一个核心,整体耗时很长。这是因为CPython有GIL,多线程在CPU密集场景下不能并行执行字节码。要把性能提上去,就需要从多线程转向多进程,用多个解释器进程吃掉多核资源。

为什么多线程在数据处理中不够用
多线程适合I/O等待多的任务,比如同时请求多个接口。但数据处理通常是算得多、等得少。下面用一段简单代码看多线程在密集计算时的表现:
import threading
import time
def compute(n):
# 模拟CPU密集计算
total = 0
for i in range(n):
total += i * i
return total
def run_threads():
threads = []
start = time.time()
for _ in range(4):
t = threading.Thread(target=compute, args=(10**7,))
t.start()
threads.append(t)
for t in threads:
t.join()
print("多线程耗时:", time.time() - start)
run_threads()
在上面例子中,四个线程仍被GIL约束,实际是轮流执行,耗时接近单线程做四次的总和。
用多进程真正利用多核
把线程换成进程,每个进程有独立GIL,就能并行跑在多个CPU核心上。推荐使用concurrent.futures.ProcessPoolExecutor。
import concurrent.futures
import time
def compute(n):
total = 0
for i in range(n):
total += i * i
return total
def run_processes():
start = time.time()
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as exe:
results = exe.map(compute, [10**7] * 4)
list(results)
print("多进程耗时:", time.time() - start)
run_processes()
同样的任务量,多进程版本在四核机器上耗时大约降到原来的四分之一。
实践中的优化注意点
任务拆分要均匀
用exe.map或exe.submit时,尽量把数据切成大小接近的块,避免一个进程分到过重任务而其他进程早退。
减少进程间数据拷贝
进程之间不共享内存,传参和返回结果都会序列化。如果处理的是大DataFrame,可以考虑分块读取后在各进程内独立加载,而不是从主进程传大对象。
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 多线程 | I/O密集 | 开销小,共享内存方便 | 受GIL限制不能并行计算 |
| 多进程 | CPU密集 | 真正多核并行 | 内存占用高,传数据有成本 |
控制 worker 数量
一般设成和CPU核心数一致。过多进程会引发调度开销和内存压力。
对于批量的本地数据清洗和转换,多进程是最直接的Python性能优化入口。
小结
面对Python数据处理性能瓶颈,先判断是I/O还是CPU主导。CPU密集时,从多线程切到多进程,配合合理的任务切分与数据传递,就能用较少改动拿到明显的加速效果。