多线程下直接用 open() 写文件会因文件指针共享、write() 非原子及缓冲不一致导致内容交错、丢失或乱码;需用 threading.lock 保护整个写入流程(含 write 和 flush),或改用 os.open() + o_append 绕过 python 缓冲。

为什么直接用 open() 写文件在多线程下会出问题
多个线程同时调用 open('file.txt', 'a') 并写入,很可能导致内容交错、丢失或乱码。这不是 Python 的 bug,而是底层系统调用(如 write())不保证原子性,且文件指针位置在多线程间共享但未同步。
常见现象包括:一行日志被截断、两行内容挤在同一行、部分写入完全消失。
- 即使使用
'a'模式,POSIX 也不保证跨进程/线程的 append 原子性(尤其在 NFS 或某些文件系统上) -
print(..., file=f)或f.write()都不是线程安全操作,f对象内部缓冲和 seek/write 分离,易被中断 - 用
threading.Lock保护整个写入块是可行起点,但要注意锁粒度——锁太粗会严重拖慢吞吐,锁太细可能漏掉关键临界区
用 threading.Lock 包裹最小写入单元最稳妥
这是最直接、兼容性最好、也最容易验证是否生效的方式。核心原则:所有对同一文件对象的 .write()、.flush()、.seek() 操作,必须在同一个锁保护下完成。
不要只锁 f.write() 却漏掉 f.flush();也不要为每个线程创建独立 open() 句柄再加锁——那会引发文件描述符竞争和偏移错乱。
- 锁对象必须是模块级或类实例级的单例,不能在线程函数里每次新建
threading.Lock() - 推荐在文件打开后就持有句柄,并复用该句柄 + 全局锁,而非每次写都
open/close - 务必用
with lock:而非手动acquire()/release(),避免异常时锁未释放
import threading
<p>log_file = open('app.log', 'a')
lock = threading.Lock()</p><p>def safe_write(msg):
with lock:
log_file.write(f'[{threading.current_thread().name}] {msg}\n')
log_file.flush() # 确保立即落盘,不依赖缓冲
</p>
避免用 queue.Queue 做日志中转的典型误用
有人试图用 queue.Queue 让工作线程把日志塞进去,再由单独一个“日志线程”消费写入——这看似解耦,实则引入新风险:
- 如果消费者线程崩溃或阻塞,队列会无限增长,吃光内存
-
Queue.put()是线程安全的,但只是把字符串塞进内存队列;真正写文件仍需锁或串行化,没减少本质竞争 - 日志时间戳、线程上下文等信息可能在入队时就丢失(比如用
time.time()入队,但实际写入延迟数秒) - 除非你明确需要异步批处理(如攒够 100 条再刷一次磁盘),否则纯属增加复杂度
真要用队列,至少加超时和大小限制:queue.Queue(maxsize=1000),并在 put() 时捕获 queue.Full 做降级(如打印到 stderr 或丢弃)。
更轻量的选择:用 os.open() + os.write() 绕过 Python 缓冲
如果你追求极致可控,且只写文本(无编码转换需求),可跳过 Python 的 io.TextIOWrapper,直接用底层系统调用。它天然规避了 TextIOWrapper 的缓冲与换行符处理干扰,配合 O_APPEND 标志能获得更强的追加语义保障(注意:仍非绝对原子,但比 Python 层更接近系统保证)。
- 必须用
os.O_APPEND | os.O_WRONLY | os.O_CREAT打开,不能用os.O_TRUNC -
os.write(fd, b'...')参数必须是bytes,记得.encode('utf-8') - 无需额外锁——
O_APPEND保证每次write()自动 seek 到末尾并写入,内核级原子 - 缺点:无法直接写
str,不能自动处理\n换行符逻辑,错误信息是OSError而非IOError
import os
<p>fd = os.open('log.bin', os.O_APPEND | os.O_WRONLY | os.O_CREAT)</p><p>def sys_write(msg):
os.write(fd, f'[{threading.current_thread().name}] {msg}\n'.encode('utf-8'))
</p>
文件描述符泄漏比锁没释放更难排查,os.open() 后记得在程序退出时 os.close(fd);而用 open() + Lock 方案,只要锁对象生命周期管理得当,是最平衡的选择。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











