python文件锁不能只靠os.o_excl,因其仅在创建新文件时原子生效,对已存在文件报错且不跨进程;必须用flock(linux/macos)或portalocker(跨平台)等内核级锁机制。

Python 写入前加锁:用 flock 最直接有效
Linux/macOS 上,flock 是最轻量、最符合 POSIX 语义的文件锁方案。它基于内核级 advisory lock,不阻塞其他进程读写(除非对方也调用 flock),但能可靠防止多个 Python 进程同时写同一文件。
关键点:锁必须在打开文件后、写入前获取,且锁对象需保持存活(不能提前 close 或被 GC 回收)。
-
flock锁是“建议性”的——只对同样使用flock的进程起作用,无法阻止echo "x" > file这类直接覆盖 - Windows 不支持
flock,会抛NotImplementedError;跨平台项目需 fallback - 不要对已关闭的文件描述符调用
flock,否则报OSError: [Errno 9] Bad file descriptor
import fcntl
import os
<p>with open("data.txt", "a") as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX) # 加排他锁
f.write("new line\n")
f.flush() # 确保内容落盘再解锁
fcntl.flock(f.fileno(), fcntl.LOCK_UN) # 解锁(可选,close 时自动释放)
</p>
Windows 下替代方案:用 msvcrt.locking 或临时文件原子重命名
Windows 没有 flock,msvcrt.locking 是唯一原生 API,但它只支持对文件**字节范围**加锁,且行为容易出错(比如锁范围越界会崩溃),实际不推荐用于全文件保护。
更稳妥的做法是绕过锁机制,改用「写临时文件 + 原子重命名」:
- 所有写操作先写到
data.txt.tmp,完成后用os.replace()原子替换原文件(Windows 和 Linux 都支持) - 该方法天然规避并发写冲突,但无法防止读进程看到旧版本(即写期间读到的是上一版)
- 注意:
os.rename()在 Windows 上跨分区会失败,必须用os.replace()
import os
import tempfile
<p>tmp_fd, tmp_path = tempfile.mkstemp(suffix=".tmp", dir=os.path.dirname("data.txt"))
try:
with os.fdopen(tmp_fd, "w") as f:
f.write("new content\n")
os.replace(tmp_path, "data.txt") # 原子覆盖
except:
os.unlink(tmp_path)
raise
</p>
避免用 threading.Lock 解决多进程文件冲突
threading.Lock 只在单个 Python 进程内有效,对 fork 出的子进程或独立启动的另一个 Python 脚本完全无效。常见误用场景:用 multiprocessing 启动多个 worker 写同一文件,却只加了线程锁。
- 现象:程序看似正常,但文件内容随机丢失或乱序,日志里看不到异常
- 根本原因:每个进程有自己的内存空间和
threading.Lock实例,互不感知 - 正确思路:多进程间同步必须依赖 OS 级原语(如
flock、信号量、文件锁文件等)
锁超时与死锁风险:别让一个进程卡住整个系统
flock 默认阻塞等待,如果持有锁的进程崩溃未释放,其他进程会无限期挂起。生产环境必须设超时。
- 用
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)尝试非阻塞加锁,捕获OSError判断是否失败 - 配合重试逻辑(如指数退避)比简单抛异常更健壮
- 绝对不要在持有文件锁时做耗时操作(如网络请求、数据库查询),否则放大阻塞影响
锁本身不解决数据一致性问题——它只保证「同一时刻最多一个写者」,但写入内容是否完整、是否校验、是否需要事务语义,得靠上层逻辑补足。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











