脚本中文件锁如何实现互斥

wen 实用脚本 2

本文目录导读:

脚本中文件锁如何实现互斥

  1. flock 命令(Linux/Unix 推荐)
  2. mkdir 原子操作(通用所有系统)
  3. ln 硬链接(避免竞态条件)
  4. lockfile 命令(需安装 procmail)
  5. PowerShell 中的文件锁(Windows)
  6. Python 中的文件锁
  7. 关键注意事项
  8. 推荐方案

利用操作系统提供的原子操作,确保只有一个进程能成功获取锁,以下是几种常见的方法:

flock 命令(Linux/Unix 推荐)

最常用且可靠的方法,通过文件描述符锁定文件。

#!/bin/bash
LOCK_FILE="/tmp/myapp.lock"
# 打开文件并获取文件描述符
exec 200>"$LOCK_FILE"
# 尝试获取锁(非阻塞)
flock -n 200 || {
    echo "另一个实例正在运行,退出"
    exit 1
}
# 或者使用阻塞等待
# flock 200 || exit 1
echo "获得锁,开始执行任务..."
sleep 30  # 模拟任务
echo "任务完成,释放锁"
# 脚本结束时自动释放锁(文件描述符关闭时)

关键点

  • exec 200>"$LOCK_FILE":打开/创建文件,文件描述符200
  • flock -n:非阻塞尝试(-n),失败立即返回
  • 锁在文件描述符关闭时自动释放

mkdir 原子操作(通用所有系统)

利用 mkdir 的原子性(要么成功创建,要么失败):

#!/bin/bash
LOCK_DIR="/tmp/myapp.lockdir"
# 尝试创建目录作为锁,mkdir是原子操作
if mkdir "$LOCK_DIR" 2>/dev/null; then
    echo "获得锁,开始执行任务..."
    trap "rmdir '$LOCK_DIR'; echo '锁已释放'" EXIT
    sleep 30  # 模拟任务
else
    echo "另一个实例正在运行,退出"
    exit 1
fi

优点:跨平台(Linux/Windows/BSD都可使用) 缺点:需要手动清理(使用 trap 确保清理)

ln 硬链接(避免竞态条件)

ln 也是原子操作,比 mkdir 更可靠:

#!/bin/bash
LOCK_FILE="/tmp/myapp.lock"
PID=$$
LOCK_TARGET="/tmp/lock_target_$PID"
# 创建唯一临时文件
touch "$LOCK_TARGET"
# 尝试创建硬链接,如果文件已存在则失败
if ln "$LOCK_TARGET" "$LOCK_FILE" 2>/dev/null; then
    echo "获得锁 $LOCK_FILE"
    trap "rm -f '$LOCK_FILE' '$LOCK_TARGET'; echo '锁已释放'" EXIT
    sleep 30  # 模拟任务
else
    echo "另一个实例正在运行"
    rm -f "$LOCK_TARGET"
    exit 1
fi

lockfile 命令(需安装 procmail)

#!/bin/bash
LOCK_FILE="/tmp/myapp.lock"
# 创建锁文件(等待10秒超时)
lockfile -r -1 -l 3600 "$LOCK_FILE" 2>/dev/null || {
    echo "无法获取锁(超时或权限问题)"
    exit 1
}
echo "获得锁,开始执行..."
# 函数结束时自动删除锁
trap "rm -f $LOCK_FILE; exit" EXIT
sleep 30

PowerShell 中的文件锁(Windows)

$lockFile = "$env:TEMP\myscript.lock"
$lockStream = $null
try {
    # 尝试打开文件,FileShare.None 表示独占访问
    $lockStream = [System.IO.File]::Open(
        $lockFile,
        [System.IO.FileMode]::OpenOrCreate,
        [System.IO.FileAccess]::ReadWrite,
        [System.IO.FileShare]::None
    )
    Write-Host "获得锁,开始执行..."
    Start-Sleep -Seconds 30  # 模拟任务
}
catch {
    Write-Host "另一个实例正在运行,退出"
    exit 1
}
finally {
    if ($lockStream -ne $null) {
        $lockStream.Close()
        Remove-Item $lockFile -Force -ErrorAction SilentlyContinue
    }
}

Python 中的文件锁

import fcntl
import sys
import time
LOCK_FILE = "/tmp/myapp.lock"
def main():
    # 打开/创建锁文件
    with open(LOCK_FILE, 'w') as f:
        try:
            # fcntl.LOCK_EX: 排他锁, LOCK_NB: 非阻塞
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            print("获得锁,开始执行...")
            # 执行任务
            time.sleep(30)
            print("任务完成,锁自动释放")
        except IOError:
            print("另一个实例正在运行,退出")
            sys.exit(1)
if __name__ == "__main__":
    main()

关键注意事项

  1. 原子操作:选择使用系统提供的原子操作(flock、mkdir、ln)
  2. 自动清理:使用 trapfinally 确保锁文件在异常时也被清理
  3. 超时机制:防止死锁,如 flock -w 10 等待10秒
  4. PID 验证:可将进程ID写入锁文件,检查进程是否存活:
    # 检查锁文件中的PID是否还在运行
    if [ -f "$LOCK_FILE" ]; then
        OLD_PID=$(cat "$LOCK_FILE")
        if kill -0 "$OLD_PID" 2>/dev/null; then
            echo "进程 $OLD_PID 还在运行"
            exit 1
        else
            echo "清除过期锁"
            rm -f "$LOCK_FILE"
        fi
    fi
    echo $$ > "$LOCK_FILE"

推荐方案

  • Linux/Unix 脚本:优先使用 flock 方案(最可靠、最简洁)
  • 跨平台脚本:使用 mkdir 方案
  • Windows/PowerShell:使用 .NET 的文件流独占访问
  • 需要复杂逻辑:使用 Python/Perl 等编程语言实现

选择合适的锁机制取决于你的操作系统和具体需求,关键是理解原子操作自动释放这两个核心概念。

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