多线程写文件:为什么你的代码还在“打架”?

多线程同时写入同一个文件,这事儿看着简单,实则是个坑。不少开发者一上来就写个循环,开几个线程,自信满满地往一个文件里“咣咣咣”写东西,结果日志一打开,错乱、覆盖、乱码,什么妖魔鬼怪都来了。问题出在哪?不是Python的锅,是POSIX文件I/O模型本身就没打算让你这么干。

Python如何避免多线程同时写入同一个文件冲突_使用threading.Lock实现文件锁

为什么直接多线程写文件会出问题

多个线程调用 open(..., 'a')f.write() 时,操作系统层面的文件偏移量(file offset)和缓冲区状态不一致,导致内容错乱、覆盖或丢失。这不是 Python 的 bug,而是 POSIX 文件 I/O 模型决定的:即使追加模式('a'),write() 系统调用本身不是原子操作,尤其在高并发小写入场景下极易复现。

常见错误现象包括:

threading.Lock 怎么用才真正起作用

关键点:锁对象必须是所有线程共享的同一个实例,且要包裹整个写入动作(打开 + 写 + 关闭),不能只锁 f.write()。否则仍可能多个线程同时执行 open(),拿到各自独立的文件句柄,锁就失效了。

正确做法:

示例:

import threading
log_lock = threading.Lock()  # 共享锁

def write_log(message):
    with log_lock:  # 必须在这里进入临界区
        with open("app.log", "a") as f:
            f.write(f"[{threading.current_thread().name}] {message}\n")

Lock 不解决但你必须知道的副作用

加锁能保数据不乱,但会显著降低吞吐量——所有写请求串行化。如果每秒写几百次,瓶颈立刻出现在锁竞争上。

更严重的是:如果某个线程在写文件时抛异常(比如磁盘满、权限被撤),而你没在 with 块里处理,锁可能被永久持有(虽然 Python 的 with 在绝大多数情况下能保证释放,但极端异常如 os._exit() 或 SIGKILL 仍可能绕过)。所以:

比 Lock 更适合日志场景的替代方案

如果你只是记日志,别自己造轮子。Python 标准库的 logging 模块默认就是线程安全的,底层已用锁保护 handler 的 emit() 方法。

直接用它更可靠:

import logging
logging.basicConfig(
    filename="app.log",
    level=logging.INFO,
    format="%(asctime)s [%(threadName)s] %(message)s"
)
logging.info("User login succeeded")  # 自动线程安全

自建 threading.Lock 只应在以下情况考虑:需要精确控制写入格式(比如二进制协议包)、必须绕过 logging 的格式化开销、或写入非文本文件(如 SQLite 的 WAL 模式除外,那该用数据库锁)。

真要自己锁文件,记住:锁的是「写行为」,不是「文件路径」;锁粒度越粗越安全,也越慢;而 logging 已经帮你平衡好了这个权衡。

本文转载于:https://www.php.cn/faq/2311376.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。