fcntl.flock不能跨进程保护追加写,因O_APPEND使内核自动seek到末尾再write,而flock锁的是fd而非追加行为本身,导致多进程write仍可能重叠;正确做法是用os.open获取fd、加锁、seek(0,2)、write、flush、解锁。

为什么 fcntl.flock 不能跨进程保护追加写(a 模式)
直接用 flock 锁住一个以 'a' 模式打开的文件对象,**并不能阻止其他进程同时写入**。根本原因是:Python 的 open('file', 'a') 在底层调用 open(2) 时带了 O_APPEND 标志,而该标志会让内核在每次 write(2) 前自动将文件偏移量移到末尾——这个动作是原子的,但 flock 锁的是整个文件描述符,不是“追加行为”本身。两个进程同时 write,可能仍会因缓冲、调度等原因导致内容重叠或换行错乱。
正确做法:先锁再 seek + write,避开 a 模式
必须放弃 'a' 模式,改用 'r+' 或 'w+' 打开文件,手动控制偏移量。关键步骤是:获取独占锁 → seek(0, 2) 到末尾 → 写入 → 解锁。否则锁和写之间存在竞态窗口。
-
flock必须作用于同一个打开的文件描述符(即不能在with open() as f:块里锁完就关文件) - 推荐用
os.open()获取原始 fd,再用os.fdopen()包装为文件对象,避免 Python 缓冲干扰 - 务必使用
fcntl.LOCK_EX | fcntl.LOCK_NB配合异常捕获,防止死等
import fcntl import osfd = os.open('log.txt', os.O_RDWR | os.O_CREAT) try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) with os.fdopen(fd, 'w') as f: f.seek(0, 2) # 移动到末尾 f.write('new line\n') f.flush() # 确保落盘 finally: fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd)
常见错误:用 with open() + flock 混用
下面这段代码看似合理,实则无效:
with open('log.txt', 'a') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
f.write('line\n') # ❌ 危险!'a' 模式下 write 自动 seek,但锁已释放(with 结束 close)
问题在于:with open() 退出时会自动 close(),而 flock 是 fd 级锁,close() 后锁立即释放;且 'a' 模式的 write 虽原子,但多个进程仍可能在 flock 和 write 之间插入写操作。
- 不要对
with open(...)返回的文件对象加锁后还依赖其上下文管理 - 不要在
'a'模式下幻想flock能串行化写入 - 日志类场景建议用
RotatingFileHandler或专用日志库,它们内部已处理锁与追加逻辑
注意 fcntl.flock 的平台限制和替代方案
flock 在 Linux/macOS 上工作正常,但在 NFS 文件系统上可能不生效;Windows 完全不支持 flock,需改用 msvcrt.locking 或 mmap + 信号量。如果目标环境不确定,优先考虑外部协调机制(如 Redis 分布式锁)或追加写专用工具(如 tee + 文件重定向)。
真正棘手的不是加锁本身,而是「锁的生命周期必须严格覆盖从定位到写入再到刷盘的全过程」——漏掉 flush() 或提前 close(),都等于没锁。










