多线程读csv几乎无效,因pandas.read_csv是cpu密集型且受gil限制,磁盘i/o常为瓶颈;推荐用processpoolexecutor进程并行或polars+threadpoolexecutor实现真正并发。

多线程对读取 CSV 文件几乎没加速效果,甚至更慢——因为 pandas.read_csv 是 CPU 密集型操作,且底层 C 实现已高度优化,Python 多线程受 GIL 限制无法并行执行;同时磁盘 I/O 在多数情况下是瓶颈,线程切换反而增加开销。
为什么 threading 读 CSV 基本无效
常见现象:启 8 个线程读 100 个 CSV,总耗时比单线程还长 20%~50%。根本原因有三:
-
read_csv的解析逻辑(如类型推断、分隔符处理)由 C 代码完成,GIL 在调用这些 C 函数时仍被持有,线程实际是串行执行 - 频繁的线程上下文切换消耗 CPU 时间,尤其当文件小、数量多时,开销占比更高
- 机械硬盘(HDD)随机读性能极差,多线程并发读会加剧磁头寻道抖动;即使是 SSD,文件系统缓存和队列策略也可能导致竞争而非协同
真正有效的替代方案:用 concurrent.futures.ProcessPoolExecutor
绕过 GIL 的唯一可靠方式是进程级并行。但要注意:不能直接把 pd.read_csv 丢进子进程——pandas 对进程间数据序列化支持有限,且每个子进程都会加载完整 pandas,内存开销陡增。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 推荐做法:在子进程中只做「原始读取 + 类型预设」,避免自动类型推断(
dtype='string'或显式指定列类型) - 关闭索引生成:
index_col=False,减少 pandas 内部对象构建开销 - 慎用
chunksize:它返回迭代器,在多进程里需手动拼接,易出错;不如一次性读完再返回DataFrame - 示例关键片段:
from concurrent.futures import ProcessPoolExecutor
import pandas as pd
def load_csv(path):
return pd.read_csv(path, dtype='string', index_col=False)
with ProcessPoolExecutor(max_workers=4) as executor:
dfs = list(executor.map(load_csv, file_paths))
更轻量、更可控的选择:用 polars + ThreadPoolExecutor
如果必须用线程(例如受限于内存、无法 fork 进程),polars 是目前唯一能在线程中真正并发读 CSV 的 Python 库——它的 Rust 后端不依赖 GIL,且默认启用多线程解析(无需额外配置)。
- 安装:
pip install polars - 直接用线程池提交
pl.read_csv,每个调用都可利用多个 CPU 核心解析自身文件 - 注意路径必须是字符串(
Path对象需先转str) - 避免混合使用 pandas 和 polars 的 DataFrame,类型转换开销可能抵消收益
- 示例:
import polars as pl
from concurrent.futures import ThreadPoolExecutor
def load_pl(path):
return pl.read_csv(path)
with ThreadPoolExecutor(max_workers=8) as pool:
lf_list = list(pool.map(load_pl, file_paths))
真正卡点往往不在“选线程还是进程”,而在于是否提前约束了列类型、是否关闭了冗余功能(如 index、infer_types)、以及是否误以为“更多 worker = 更快”——超过物理核心数的进程/线程只会加剧调度争抢。实测中,4~6 个 worker 通常是吞吐拐点,再往上收益趋近于零。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










