snakemake 本身不直接锁定整个输出目录,但可通过将目录声明为 output: directory("path") 实现逻辑级互斥;需注意该操作会触发目录递归清理,可能破坏动态生成的中间文件。
snakemake 本身不直接锁定整个输出目录,但可通过将目录声明为 output: directory("path") 实现逻辑级互斥;需注意该操作会触发目录递归清理,可能破坏动态生成的中间文件。
在 Snakemake 工作流中,文件级锁定(file-level locking)是默认且可靠的行为:当某条规则声明了具体输出文件(如 output: "results/data.txt"),Snakemake 会在执行前对该文件加锁,防止其他并发作业同时写入同一文件,从而保障数据一致性。
然而,当涉及动态生成、数量不定、路径不可预知的一组文件共存于同一目录(例如批量处理产生的 tmp/001.bin, tmp/002.bin, …),仅靠声明单个文件无法覆盖全部场景。此时,开发者常考虑将整个目录设为输出项,以期获得“目录级锁定”效果。
✅ 正确做法:使用 directory() 函数显式声明目录为输出
rule process_batch:
output:
directory("tmp/batch_run")
shell:
"mkdir -p {output} && "
"some_tool --output-dir {output} --input-list {input}"Snakemake 将该目录视为一个原子性输出单元:
- 在作业启动前,检查并尝试获取该目录的锁(基于文件系统层面的 .snakemake/locks/ 锁文件);
- 若另一作业正占用该目录,当前作业将等待或报错(取决于 --rerun-incomplete 和锁策略);
- 该锁定行为作用于目录路径本身,不递归检查或锁定其内部文件——即它保证“至多一个作业正在写入此目录”,但不保证目录内任意子文件被独占访问(除非这些子文件也被单独列为 output)。
⚠️ 关键限制:directory() 输出会触发强制递归清理
根据 Snakemake 官方文档,只要目录被列为 output,Snakemake 在执行规则前会无条件删除整个目录及其所有内容(包括子目录、隐藏文件、临时缓存等)。这意味着:
- 若你的流程依赖目录中已存在的、由上游动态生成的中间文件(未被显式声明为 input),它们将被静默清除;
- 不适用于“增量写入”或“多阶段共享目录”的场景(如多个 rule 共同向 logs/ 写入不同日志文件);
- 无法替代真正的文件系统级目录锁(如 flock 或 mkdir 原子性检测)。
? 替代方案建议(针对动态文件集场景):
- 使用 shadow 目录隔离:启用 shadow: "shallow" 或 "full",使每个作业在独立副本中运行,避免跨作业目录竞争;
- 外部同步机制:在 shell 或 run 块中集成轻量级锁(如 flock /tmp/mydir.lock);
- 统一输出命名 + 显式 input 声明:通过 expand() + dynamic()(已弃用)或更推荐的 checkpoint + glob_wildcards 动态发现文件,并将其全部纳入 input/output,让 Snakemake 自动推导依赖与锁;
- 专用锁文件规则:定义一条低开销的 lock_dir rule,生成 tmp/batch_run/.locked 文件作为信号量,其他 rule 依赖该文件。
? 总结:
output: directory("mydir") 是实现目录粒度调度互斥的有效手段,适合“独占创建+完全控制目录内容”的场景(如一次性产出完整结果集);但它不是“只读保护”或“细粒度文件锁”,更不提供运行时文件级排他访问。务必评估清理行为对工作流的影响——若目录需长期存在或被多方写入,请优先选用 shadow、checkpoint 或外部同步机制。










