macOS 上 flock 不生效因其仅为建议性锁且不跨进程持久,Python 中应改用 os.open(..., O_CREAT | O_EXCL) 实现可移植文件锁,或选用 portalocker 库处理平台差异。

Python 中 flock 为什么在 macOS 上不生效
因为 macOS 的 flock 系统调用是“ advisory only ”(仅建议性),且不跨进程持久,尤其在 NFS 或某些文件系统上完全失效。更关键的是,Python 标准库的 msvcrt.locking(Windows)和 fcntl.flock(Linux/macOS)行为不一致——macOS 上即使调用成功,另一个 Python 进程仍可能绕过锁写入文件。
实际开发中,若你在 macOS 测试时发现两个脚本同时写同一个日志文件却没报错,大概率就是这个原因。别依赖 flock 做强互斥。
- Linux 下可用
flock+ 子进程方式实现较可靠排他(如flock -x file -c "python writer.py") - 跨平台方案应避开内核级文件锁,改用基于文件存在性或原子重命名的用户态锁
- 测试时务必在目标部署环境(而非仅本地 macOS)验证锁行为
用 os.open + os.O_EXCL 实现可移植的文件锁
这是目前最轻量、兼容性最好的纯 Python 文件锁思路:利用“创建临时锁文件”这一原子操作来抢占资源。只要目标目录支持 POSIX 语义(绝大多数本地文件系统都支持),os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY) 就能保证只有一个进程成功。
注意不能用 open(..., 'x') —— 它在部分 Python 版本或 Windows 上对网络路径支持不稳定;而 os.open 更底层、行为更可控。
立即学习“Python免费学习笔记(深入)”;
- 锁文件路径建议与被保护文件同目录,后缀用
.lock(如data.json.lock) - 必须配合
try/finally或contextlib.closing确保锁文件被显式os.close()和os.unlink() - 不要依赖锁文件内容,只用其存在性判断;写入 PID 是可选增强,但需额外处理僵尸锁
portalocker 库是否值得引入
值得,但只在你已确认需要「自动处理平台差异 + 锁超时 + 共享/独占模式切换」时再用。它封装了 flock、msvcrt.locking、fcntl.lockf 三层逻辑,并提供了 portalocker.lock 和上下文管理器接口。
不过要注意:它的超时机制本质是轮询 + time.sleep,不是真正的异步等待;且在容器或 chroot 环境中,若 /tmp 不可写,它默认的临时锁路径会失败。
- 安装:
pip install portalocker - 基础用法:
with portalocker.Lock('shared.dat', 'w') as fh: fh.write('data') - 避免在高并发短任务中设
timeout=0,容易因调度延迟导致误判失败
哪些场景根本不该用文件锁
当你的程序本身是单进程、或数据访问已由数据库/Redis 等外部服务协调时,硬加文件锁反而增加故障点。典型反例包括:
- Django 的
manage.py collectstatic被多个 CI job 并发触发 —— 应该靠 CI 系统串行化,而非在代码里锁 static/ 目录 - Flask 启动多个 gunicorn worker 写同一份缓存文件 —— 正确做法是用
redis-py或diskcache替代文件缓存 - 多线程写本地 CSV —— 线程间用
threading.Lock即可,文件锁解决的是进程级竞争,过度使用会拖慢 I/O
文件锁真正该出场的地方很窄:多个独立 Python 进程(非父子关系)、无中央协调服务、且必须直接读写同一文件 —— 比如定时任务脚本和后台守护进程共管一个配置快照。










