Python 文件与锁的统一管理方式

13次阅读

python 中 threading.lock 不能跨进程生效,应使用 multiprocessing.lock 或 filelock 库;filelock 自动适配线程 / 进程场景,将锁与文件路径强绑定,避免手动管理错误。

Python 文件与锁的统一管理方式

Python 中 threading.Lock 不能跨进程生效

如果你在多进程(multiprocessing)场景下直接复用 threading.Lock,程序不会报错,但锁完全失效——两个进程能同时写入同一个文件。这是因为 threading.Lock 只作用于当前 Python 解释器线程,不共享内存空间。

真正该用的是 multiprocessing.Lock,它底层基于系统级信号量或文件锁机制,能跨进程同步。但注意:它只保证“加锁 / 解锁”操作原子,不自动绑定到某个文件路径上——你得自己把锁和文件关联起来。

  • 多线程(单进程)→ 用 threading.Lock
  • 多进程(含 multiprocessing.Processconcurrent.futures.ProcessPoolExecutor)→ 必须用 multiprocessing.Lock
  • 混合场景(如主线程 + 多进程子进程)→ 锁必须由主进程创建并传入子进程,不能在子进程中新建

filelock 库统一处理文件读写竞争

手动管理 multiprocessing.Lock 和文件路径容易出错:比如忘记加锁、锁粒度太粗(整个程序只一个锁)、或锁对象没正确传递。更稳的方式是用 filelock ——它把锁和文件路径强绑定,且自动适配线程 / 进程场景。

安装后,FileLock 会根据运行环境自动选择 threading.Lock(单进程)或基于文件的独占锁(多进程),无需条件判断。

立即学习 Python 免费学习笔记(深入)”;

from filelock import FileLock  with FileLock("data.json.lock"):     with open("data.json", "r+") as f:         data = json.load(f)         data["count"] += 1         f.seek(0)         json.dump(data, f)         f.truncate()
  • FileLock 的参数是锁文件路径,不是目标文件路径;习惯上加 .lock 后缀
  • 锁文件和目标文件最好放在同一目录,避免权限或挂载点差异导致锁失效
  • Windows 下注意路径大小写不敏感,但锁文件名仍需严格一致

flock 系统调用在 Python 中的可靠替代方案

有人想直接用 Linux 的 flock 系统调用做文件锁,但在 Python 里靠 fcntl.flock 实现时,容易踩两个坑:一是锁随文件描述符关闭而自动释放(比如 with open()…… 结束后锁就丢了),二是 NFS 文件系统可能不支持 flock

所以不建议手写 fcntl.flock,除非你明确控制文件描述符生命周期,且确认运行环境支持。更稳妥的做法是依赖 filelock(它内部在 POSIX 系统优先用 flock,失败则降级为 mkdir 原子性模拟)。

  • 不要在 open()with 块内调用 fcntl.flock,锁生命周期不可控
  • 若必须用原生 flock,应保持 fd 打开状态,直到所有读写完成
  • Docker 容器中若挂载了 host 的 NFS 卷,flock 很可能静默失效,filelock 的降级逻辑此时更重要

锁的粒度与性能权衡:别让一个锁卡住所有文件操作

很多人图省事,整个应用只用一个全局锁,比如所有文件都等同一个 FileLock("global.lock")。这会导致本可并发的 I/O 操作被串行化,吞吐量骤降。

合理做法是按文件路径或业务域分锁:每个文件(或一类配置文件)对应独立锁实例。如果锁名动态生成,注意避免路径遍历或特殊字符引发冲突。

  • 锁名建议哈希化:例如 FileLock(f"{hashlib.md5(path.encode()).hexdigest()}.lock")
  • 避免锁名含绝对路径(不同用户 / 环境路径不同),改用相对路径或业务标识符
  • 日志类高频写入文件,适合用细粒度锁;而配置文件读多写少,可考虑读写锁(readerwriterlock 库)

锁本身不贵,贵的是等待。真正影响性能的从来不是锁的创建,而是谁在等、等多久——这点最容易被忽略。

text=ZqhQzanResources