别再让脚本“撞车”——用 WaitLock 给 Shell 加个红绿灯

当多个脚本同时读写一份数据库、同一块磁盘,甚至同一台 GPU 时,结果常常是一场灾难:备份文件被覆盖、下载任务互相抢占、日志里塞满“device busy”。
WaitLock 这个小工具就像十字路口的红绿灯,帮你在 Shell 层面实现 互斥锁信号量,让多进程井然有序。本文带你从零安装到高阶实战,一次看懂它的全部玩法。


Shell scripts waiting in line

为什么会有 WaitLock?

日常写脚本时,我们常用 flocklockfile 等命令给文件加锁,但它们要么语法冗长,要么在进程异常退出时留下“僵尸锁”。
WaitLock 的设计初衷就是解决这些痛点:


  • 语法简洁:一条命令就能加锁、执行脚本、自动释放。

  • 自动清理:进程崩溃、被杀、正常退出都会解锁,不怕“僵尸”。

  • 跨平台:纯 C 写成,Linux、macOS、*BSD 都能跑。

  • 两种模式


    • 互斥锁(mutex):同一时刻只允许一个进程干活。

    • 信号量(semaphore):允许 N 个进程并行,适合做线程池、GPU 池。

Multiple terminals running scripts

安装:三步搞定

1. 准备编译环境

Ubuntu / Debian

sudo apt-get update
sudo apt-get install build-essential autoconf

CentOS / Rocky

sudo yum groupinstall "Development Tools"
sudo yum install autoconf

macOS

# 先装 Xcode Command Line Tools
xcode-select --install
# 推荐用 Homebrew 装 autoconf
brew install autoconf

2. 下载并编译

git clone https://github.com/user/waitlock.git
cd waitlock

# 如果你拿到的是源码压缩包,直接解压即可
autoreconf -fi          # 只在 Git 仓库里需要
./configure
make

3. 安装到系统

sudo make install
# 默认装到 /usr/local/bin/waitlock
# 想换路径:
# ./configure --prefix=/opt/waitlock
# make install

装完后,敲 waitlock --version 能看到 1.0.0 即成功。


Command line installation

一分钟上手

场景 1:每天凌晨只跑一次备份

#!/bin/bash
# backup.sh

# 如果已有备份在跑,直接退出
waitlock nightly_backup || { echo "备份已在进行中"; exit 1; }

mysqldump --all-databases > all.sql
tar czf all.sql.tgz all.sql

执行:

chmod +x backup.sh
./backup.sh &
./backup.sh   # 第二次会提示“备份已在进行中”

场景 2:允许 4 个并行下载

#!/bin/bash
# download.sh

waitlock --allowMultiple 4 download_slot || { echo "下载队列已满"; exit 1; }

wget "$1"

配合 xargs 实现批量并行下载:

cat url.list | xargs -n1 -P8 ./download.sh

当活跃进程达到 4 个时,其余脚本自动排队。


Traffic light controlling flow

命令速查表:用哪条参数做什么

想做啥 命令示例
独占锁 waitlock mylock
最多 3 个并发 waitlock -m 3 mylock
按 CPU 核心数并发 waitlock -c mylock
预留 2 核给系统 waitlock -c -x 2 mylock
超时 30 秒 waitlock -t 30 mylock
检查锁是否可用 waitlock --check mylock
执行完命令自动解锁 waitlock mylock --exec "./run.sh"
查看所有锁 waitlock --list
只看僵尸锁 waitlock --list --stale-only

高阶玩法:把 WaitLock 用到极致

1. 基于环境变量的全局配置

/etc/profile.d/waitlock.sh 放一行:

export WAITLOCK_TIMEOUT=300
export WAITLOCK_DIR="/var/lock/myapp"

此后任何脚本都不用写 -t 300,默认 5 分钟超时,统一管理。


Global settings illustration

2. 在 CI/CD 里防止并发部署

GitLab CI 例子:

deploy:
  stage: deploy
  script:
    - waitlock deploy_lock --timeout 600 --exec ./deploy.sh

当多人同时 push 时,只有第一个 pipeline 真正执行部署,其余排队或超时失败,避免生产环境冲突。


3. GPU 资源池

实验室有 8 张显卡,希望每人最多用 2 张,且系统留 1 张:

waitlock -c -x 1 gpu_pool

export CUDA_VISIBLE_DEVICES=$WAITLOCK_SLOT
python train.py

$WAITLOCK_SLOT 会自动变成 0–6 之间的一个数字,脚本零改动。


GPU server room

4. NFS 共享目录锁

多台节点共用 NFS:

export WAITLOCK_DIR="/mnt/nfs/locks"
waitlock cluster_job --timeout 300 --exec ./mpi_job.sh

WaitLock 的文件锁在 NFS 上同样有效,实现跨主机互斥。


5. 批量处理文件并控制并发

把 1000 个 CSV 交给 3 个进程并行处理:

find /data -name "*.csv" | \
  xargs -P10 -I{} waitlock -m3 processor --exec "python process.py {}"

xargs -P10 一次起 10 个进程,WaitLock 再把并发度降到 3。


Factory assembly line

常见疑问与排查

现象 原因 解决
Permission denied 没权限写 /var/lock/waitlock export WAITLOCK_DIR=$HOME/.waitlock
锁一直不释放 进程僵尸或被杀 waitlock --list --stale-only 查看;WaitLock 会在下一次访问时自动清理
高并发时慢 目录扫描 O(n) 把锁目录挂到 tmpfs,或按业务分层命名
调试信息太少 默认静默模式 WAITLOCK_DEBUG=1 waitlock -v mylock

性能小贴士

  1. 锁目录放 tmpfs
    如果机器内存充裕,把 /var/lock/waitlock 挂到 tmpfs,减少磁盘 IO。

    sudo mount -t tmpfs -o size=10M tmpfs /var/lock/waitlock
    
  2. 层级命名避免单目录文件过多
    project/subsystem/task 这样的描述符,减少单次扫描文件数。

  3. 合理设置超时
    太短容易误杀,太长会拖慢队列。根据任务平均耗时调整。


Speedometer

写在最后

WaitLock 把“锁”这件事做到了极简:


  • 一条命令,解决并发冲突。

  • 进程退出,自动打扫战场。

  • 支持互斥、信号量、CPU 感知、NFS 共享,几乎涵盖日常脚本的所有场景。

下次写备份、跑训练、发版时,不妨把 waitlock 当成习惯动作——就像进地铁先刷卡一样自然。

祝你的脚本从此不再“撞车”。


Green light on highway

参考链接


  • 项目主页与源码:https://github.com/user/waitlock

  • 安装后阅读:man waitlock