posix文件偏移量与缓冲区不同步是根本原因:多线程调用open("log.txt", "a")时,各线程持有独立文件对象,write()非原子操作导致seek-write-update三步被中断,引发日志截断、乱码或覆盖;threading.lock必须包裹open到close全过程,仅锁write无效;高频场景宜用queue.queue实现单写线程串行化;fcntl.flock提供跨进程文件锁但不支持windows。

POSIX文件偏移量与缓冲区不同步是根本原因
多个线程调用 open("log.txt", "a") 时,每个线程拿到的是独立的文件对象(file),它们各自维护自己的缓冲区和内核级文件偏移量。即使模式是追加("a"),write() 系统调用本身不是原子操作——它先 seek 到末尾、再 write、再 update offset,三步之间可能被其他线程打断。
典型表现是:一行完整日志被切成两半写入,比如 "user_id=123, status=success" 变成 "user_id=123, stat" 和 "us=success" 两行;或末尾出现 \x00、乱码、重复内容。
这不是 Python 实现缺陷,而是 POSIX 文件 I/O 模型决定的——操作系统不保证多进程/多线程对同一文件描述符的并发 write 是安全的。
threading.Lock 必须包裹 open 到 close 全过程
只锁 f.write() 没用:因为 open() 已经返回了各自的文件句柄,后续 write 都在不同句柄上跑,锁根本拦不住。
正确做法是让所有线程共享同一个 threading.Lock() 实例,并用 with lock: 包裹整个文件操作链:
-
open()→ -
f.write()→ -
f.close()(或with open(...) as f:自动关闭)
示例关键片段:
log_lock = threading.Lock() # 模块级定义,不是每次函数里 new 一个
<p>def safe_write(message):
with log_lock: # 进入临界区
with open("app.log", "a", encoding="utf-8") as f:
f.write(f"[{threading.current_thread().name}] {message}\n")</p>
queue.Queue 方案更适合高频写入场景
当每秒写入次数超过几十次,threading.Lock 会成为明显瓶颈——所有线程排队等锁,吞吐量断崖式下降。
Python Linux版 为 Python.org 官方提供的 Python 3.14.6 Linux/Unix 源码包,适合在Linux/Unix环境中安装、运行 Python 代码并学习函数、模块和脚本开发。
这时应改用生产者-消费者模型:其他线程往 queue.Queue() 里 put 写入任务,单个专用 writer 线程从队列取、批量 or 逐条写入。
好处是:
- 写操作完全串行化,但无锁竞争
- 可以加缓冲(如每 10 条 flush 一次)提升 I/O 效率
- writer 线程设为
daemon=True,避免主程序退出卡住
注意:不要在 writer 线程里用 while not queue.empty(): ——这不可靠,要用 queue.get(timeout=...) 或配合 sentinel 值退出。
fcntl.flock 在 Linux/macOS 上提供内核级文件锁
threading.Lock 是进程内线程锁,不跨进程;而 fcntl.flock() 是操作系统级 advisory lock,能防止多个 Python 进程(甚至不同语言进程)同时写同一文件。
但它有局限:
- Windows 不支持
fcntl,得换用mmap或第三方库如filelock - 必须在
open()后、write()前调用fcntl.flock(f.fileno(), fcntl.LOCK_EX) - 锁随文件描述符关闭自动释放,所以务必用
try/finally或with确保解锁
常见误用是忘了 fcntl.LOCK_UN,导致文件被“锁死”,后续进程无法写入。
真正容易被忽略的是:锁解决的是并发写错乱,但不解决写入顺序语义。比如线程 A 先调用写函数、线程 B 后调用,但 B 的内容可能先落盘——除非你在业务逻辑里显式控制提交时机或加序号标记。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










