本文目录导读:

- 使用
mkdir原子性操作(推荐) - 使用
flock命令(Linux专用) - 使用
lockfile命令(procmail包) - 使用 PID 文件(简单但不够安全)
- 高级用法:可重入锁(单进程多实例)
- 实用的封装函数
- 选择建议
- 关键注意事项
在Shell脚本中实现互斥锁主要有以下几种方法:
使用 mkdir 原子性操作(推荐)
mkdir 是原子操作,这是最常用且可移植性最好的方法:
#!/bin/bash
LOCK_DIR="/tmp/mylock.$$"
LOCK_NAME="my_script_lock"
# 尝试获取锁
if ! mkdir "$LOCK_DIR/$LOCK_NAME" 2>/dev/null; then
echo "无法获取锁,脚本已在运行中"
exit 1
fi
# 确保在脚本退出时释放锁
trap 'rm -rf "$LOCK_DIR/$LOCK_NAME"' EXIT
# 临界区代码
echo "执行需要互斥的操作..."
sleep 10
# 锁会在脚本退出时自动释放
使用 flock 命令(Linux专用)
flock 是Linux提供的文件锁工具,更加灵活:
#!/bin/bash
LOCK_FILE="/tmp/my_script.lock"
# 尝试获取锁,不会阻塞
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
echo "无法获取锁,脚本已在运行中"
exit 1
fi
# 自动释放锁(文件描述符关闭时)
trap 'rm -f "$LOCK_FILE"' EXIT
# 临界区代码
echo "执行需要互斥的操作..."
sleep 10
flock 的变体用法:
# 阻塞等待锁(直到获取到锁)
flock 200 || exit 1
# 设置超时(等待10秒)
flock -w 10 200 || { echo "等待锁超时"; exit 1; }
使用 lockfile 命令(procmail包)
#!/bin/bash
LOCK_FILE="/tmp/my_script.lock"
# 尝试创建锁文件(创建失败表示有锁)
if ! lockfile -r 0 "$LOCK_FILE"; then
echo "无法获取锁,脚本已在运行中"
exit 1
fi
# 清理锁
trap 'rm -f "$LOCK_FILE"' EXIT
# 临界区代码
echo "执行需要互斥的操作..."
使用 PID 文件(简单但不够安全)
#!/bin/bash
LOCK_FILE="/var/run/my_script.pid"
# 检查锁文件
if [ -f "$LOCK_FILE" ]; then
PID=$(cat "$LOCK_FILE")
if kill -0 "$PID" 2>/dev/null; then
echo "脚本已在运行中 (PID: $PID)"
exit 1
fi
echo "发现过期的锁文件,清理..."
rm -f "$LOCK_FILE"
fi
# 创建锁文件
echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
# 临界区代码
echo "执行需要互斥的操作..."
高级用法:可重入锁(单进程多实例)
#!/bin/bash
LOCK_DIR="/tmp/myapp.lock"
LOCK_NAME="resource.lock"
LOCK_COUNTER="${LOCK_DIR}/${LOCK_NAME}.counter"
init_lock() {
mkdir -p "$LOCK_DIR"
}
# 可重入锁(同一个进程可以多次获取)
acquire_lock() {
local pid=$$
# 检查是否已经持有锁
if [ -f "$LOCK_COUNTER" ] && [ "$(cat "$LOCK_COUNTER")" = "$pid" ]; then
# 同一个进程,增加计数
local count=$(cat "${LOCK_DIR}/${LOCK_NAME}.count" 2>/dev/null || echo 0)
echo $((count + 1)) > "${LOCK_DIR}/${LOCK_NAME}.count"
return 0
fi
# 尝试获取锁
if mkdir "$LOCK_DIR/$LOCK_NAME" 2>/dev/null; then
echo "$pid" > "$LOCK_COUNTER"
echo 1 > "${LOCK_DIR}/${LOCK_NAME}.count"
return 0
fi
return 1
}
release_lock() {
local count=$(cat "${LOCK_DIR}/${LOCK_NAME}.count" 2>/dev/null || echo 0)
if [ "$count" -le 1 ]; then
# 完全释放锁
rm -rf "$LOCK_DIR/$LOCK_NAME"
rm -f "$LOCK_COUNTER"
rm -f "${LOCK_DIR}/${LOCK_NAME}.count"
else
# 减少计数
echo $((count - 1)) > "${LOCK_DIR}/${LOCK_NAME}.count"
fi
}
# 使用示例
init_lock
if acquire_lock; then
echo "获取锁成功"
# 临界区代码
sleep 5
release_lock
else
echo "获取锁失败"
exit 1
fi
实用的封装函数
#!/bin/bash
# 互斥锁工具函数
LOCK_DIR="/tmp/script_locks"
mkdir -p "$LOCK_DIR" 2>/dev/null
# 获取锁
# 参数: $1 锁名称, $2 超时时间(秒,可选)
acquire_lock() {
local lock_name="$1"
local timeout="${2:-0}"
local lock_path="$LOCK_DIR/$lock_name"
local start_time=$(date +%s)
while true; do
if mkdir "$lock_path" 2>/dev/null; then
echo "$$" > "$lock_path/pid"
return 0
fi
# 检查是否过期(可选)
if [ -f "$lock_path/pid" ]; then
local pid=$(cat "$lock_path/pid")
if ! kill -0 "$pid" 2>/dev/null; then
# 过期的锁,清理后重试
rm -rf "$lock_path"
continue
fi
fi
# 检查超时
if [ "$timeout" -gt 0 ]; then
local current_time=$(date +%s)
if [ $((current_time - start_time)) -ge "$timeout" ]; then
return 1
fi
else
# 无超时设置,直接返回失败
return 1
fi
sleep 0.1
done
}
# 释放锁
release_lock() {
local lock_name="$1"
local lock_path="$LOCK_DIR/$lock_name"
rm -rf "$lock_path"
}
# 使用示例
if acquire_lock "my_script" 5; then
# 确保退出时释放锁
trap 'release_lock "my_script"' EXIT
echo "执行互斥操作..."
sleep 10
else
echo "无法获取锁"
exit 1
fi
选择建议
- 推荐使用
mkdir方法 - 简单可靠,可移植性好 - Linux 环境推荐
flock- 功能强大,支持超时和阻塞等待 - 避免使用 PID 文件 - 容易出问题(PID重用等)
- 需要重入锁时使用计数器方案
关键注意事项
- 始终使用
trap确保锁被释放 - 处理脚本被
kill -9等信号时的情况(无法捕获) - 考虑使用锁文件超时机制防止死锁
- 注意文件系统的挂载类型(NFS等分布式文件系统不支持
mkdir原子性)