多线程并发写文件的关键是锁住整个I/O生命周期,而非仅write();推荐用logging模块或生产者-消费者模型替代手写文件操作。

多线程并发写同一文件时,冲突本质不是“要不要加锁”,而是“锁住什么、怎么锁、锁多久”——锁得不对或不全,隔离就形同虚设。故障隔离的关键,在于把写操作变成原子的、互斥的、可预测的临界区。
必须锁住整个I/O生命周期,不能只锁write()
常见错误是只对f.write()加锁,而忽略open()和close()。多个线程同时调用open("log.txt", "a"),会各自拿到独立文件句柄,POSIX层的文件偏移量(file offset)彼此不感知,即使内容追加到末尾,也可能因缓冲区不同步导致覆盖、截断或乱码。正确做法是:用同一个threading.Lock实例,包裹从打开、写入到关闭的全过程。
- Python示例中,锁对象需定义在模块级或类属性中,而非每个线程内新建
- 使用
with lock:确保异常时也能释放锁 - 避免
print(..., file=f)这类隐式写法,统一用f.write() + "\n"
优先用线程安全的日志模块替代手写文件操作
自己实现文件锁容易出错,而logging模块默认就是线程安全的:它内部已用锁保护底层I/O,且支持按行原子写入、自动换行、格式化分离。只要所有线程共用同一个Logger实例(非各自新建),就能天然规避交织、丢失、空行等问题。
- 配置
FileHandler时指定mode="a"(追加)即可,无需手动管理打开/关闭 - 若需更高性能,可搭配
QueueHandler + QueueListener转为异步模式,主线程只投递日志,单独线程批量落盘 - 不推荐用
RotatingFileHandler在多线程下做轮转——轮转动作本身需额外同步,易引发竞态
跨进程场景必须升级为文件系统级锁
线程锁(如threading.Lock)仅在单进程内有效。若程序以多进程方式部署(如gunicorn多worker、multiprocessing启动),就得依赖操作系统提供的文件锁机制,例如flock()(Linux/macOS)或msvcrt.locking()(Windows)。
-
flock(fd, LOCK_EX)是对文件描述符加独占锁,能阻塞其他进程的同文件访问 - 注意:
flock锁的是整个文件,不能锁某段内容;且基于文件描述符,不是文件路径 - Python中需先
os.open()获取fd,再flock,最后os.fdopen()包装为文件对象,写完os.close()自动解锁
更彻底的隔离:绕过共享文件,改用无锁队列+单写线程
当并发量高、延迟敏感或写入频繁时,锁本身会成为瓶颈。此时可放弃“多线程直写文件”的思路,改用生产者-消费者模型:
- 所有工作线程将日志消息(字符串或结构化字典)推入一个线程安全队列(如
queue.Queue) - 由唯一一个专用日志线程消费队列,串行执行
open → write → close或复用open句柄 - 该方案天然消除锁竞争,还能批量写入、压缩IO、添加时间戳/线程ID等元信息

















