Shell脚本如何实现互斥锁

wen 实用脚本 1

本文目录导读:

Shell脚本如何实现互斥锁

  1. 使用 mkdir 原子性操作(推荐)
  2. 使用 flock 命令(Linux专用)
  3. 使用 lockfile 命令(procmail包)
  4. 使用 PID 文件(简单但不够安全)
  5. 高级用法:可重入锁(单进程多实例)
  6. 实用的封装函数
  7. 选择建议
  8. 关键注意事项

在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

选择建议

  1. 推荐使用 mkdir 方法 - 简单可靠,可移植性好
  2. Linux 环境推荐 flock - 功能强大,支持超时和阻塞等待
  3. 避免使用 PID 文件 - 容易出问题(PID重用等)
  4. 需要重入锁时使用计数器方案

关键注意事项

  • 始终使用 trap 确保锁被释放
  • 处理脚本被 kill -9 等信号时的情况(无法捕获)
  • 考虑使用锁文件超时机制防止死锁
  • 注意文件系统的挂载类型(NFS等分布式文件系统不支持 mkdir 原子性)

抱歉,评论功能暂时关闭!