Shell脚本如何实现信号量同步

wen 实用脚本 4

本文目录导读:

Shell脚本如何实现信号量同步

  1. 使用文件作为信号量
  2. 使用flock进行文件锁
  3. 使用mkfifo命名管道
  4. 使用System V信号量 (需要ipcs命令)
  5. 使用命名管道实现读者-写者问题
  6. 使用临时文件实现计数信号量
  7. 高级封装示例
  8. 使用建议

在Shell脚本中实现信号量同步,可以通过以下几种方式:

使用文件作为信号量

基础示例

#!/bin/bash
# 信号量文件
SEM_FILE="/tmp/my_semaphore"
# 获取信号量
acquire() {
    while ! mkdir "$SEM_FILE" 2>/dev/null; do
        sleep 1
    done
}
# 释放信号量
release() {
    rmdir "$SEM_FILE" 2>/dev/null
}
# 使用示例
echo "尝试获取信号量..."
acquire
echo "获取到信号量,执行临界区代码"
sleep 3  # 模拟临界区操作
release
echo "释放信号量"

使用flock进行文件锁

写锁示例

#!/bin/bash
# 使用flock实现信号量
LOCK_FILE="/tmp/myapp.lock"
# 获取排他锁(等待其他进程释放)
(
    flock -x -w 10 200 || exit 1  # 等待10秒
    echo "进程 $$ 获取到锁"
    # 临界区代码
    sleep 5
    echo "进程 $$ 释放锁"
) 200>$LOCK_FILE
# 非阻塞尝试获取锁
(
    flock -x -n 200 || {
        echo "无法获取锁,进程退出"
        exit 1
    }
    echo "进程 $$ 获取到锁"
    sleep 3
    echo "释放锁"
) 200>$LOCK_FILE

共享锁示例(多个读取者)

#!/bin/bash
LOCK_FILE="/tmp/shared_resource.lock"
# 获取共享锁(读锁)
function acquire_shared_lock() {
    (
        flock -s -w 5 200 || {
            echo "无法获取共享锁"
            exit 1
        }
        echo "获取共享锁成功, PID: $$"
        # 读取操作
        cat "$LOCK_FILE" 2>/dev/null
    ) 200>$LOCK_FILE
}
# 获取排他锁(写锁)
function acquire_exclusive_lock() {
    (
        flock -x -w 5 200 || {
            echo "无法获取排他锁"
            exit 1
        }
        echo "获取排他锁成功, PID: $$"
        # 写入操作
        echo "Updated by PID $$" > "$LOCK_FILE"
    ) 200>$LOCK_FILE
}
# 清理锁
function release_lock() {
    flock -u 200 2>/dev/null
}

使用mkfifo命名管道

基于管道的信号量

#!/bin/bash
SEM_NAME="/tmp/my_semaphore_fifo"
# 初始化信号量
if [ ! -p "$SEM_NAME" ]; then
    mkfifo "$SEM_NAME"
    # 初始计数(示例:3个可用资源)
    for i in {1..3}; do
        echo "token" > "$SEM_NAME" &
    done
fi
# P操作(获取信号量)
P() {
    read token < "$SEM_NAME"
    echo "获取资源: $token"
}
# V操作(释放信号量)
V() {
    echo "token" > "$SEM_NAME" &
    echo "释放资源"
}
# 使用示例
echo "尝试获取资源..."
P
echo "使用资源中..."
sleep 2
V

使用System V信号量 (需要ipcs命令)

IPC信号量实现

#!/bin/bash
# 创建信号量
create_semaphore() {
    local key=$1
    local count=$2
    # 使用ipcrm清理旧的信号量
    ipcrm -S "$key" 2>/dev/null
    # 创建新的信号量集
    ipcs -Q --create "$key" "$count" 2>/dev/null || {
        # 使用semtool或自定义工具
        echo "Creating semaphore with key: $key, count: $count"
    }
}
# 信号量操作函数
sem_wait() {
    local sem_id=$1
    # 使用ipcrm或自定义脚本操作信号量
    # 这里简化实现
    while true; do
        # 尝试信号量操作
        if ipcs -i "$sem_id" -O 2>/dev/null; then
            break
        fi
        sleep 1
    done
}
sem_signal() {
    local sem_id=$1
    # 释放信号量
    ipcs -i "$sem_id" -o 2>/dev/null
}

使用命名管道实现读者-写者问题

读者优先实现

#!/bin/bash
#!/bin/bash
READER_FIFO="/tmp/reader_fifo"
WRITER_FIFO="/tmp/writer_fifo"
READ_COUNT=0
READ_COUNT_LOCK="/tmp/read_count.lock"
# 初始化
init_fifos() {
    [ -p "$READER_FIFO" ] || mkfifo "$READER_FIFO"
    [ -p "$WRITER_FIFO" ] || mkfifo "$WRITER_FIFO"
}
# 读者获取读权限
reader_acquire() {
    (
        flock -x 200
        READ_COUNT=$((READ_COUNT + 1))
        if [ "$READ_COUNT" -eq 1 ]; then
            # 第一个读者阻止写者
            echo "block" > "$WRITER_FIFO"
        fi
    ) 200>$READ_COUNT_LOCK
}
# 读者释放读权限
reader_release() {
    (
        flock -x 200
        READ_COUNT=$((READ_COUNT - 1))
        if [ "$READ_COUNT" -eq 0 ]; then
            # 最后一个读者释放写者
            echo "unblock" < "$WRITER_FIFO" &
        fi
    ) 200>$READ_COUNT_LOCK
}
# 写者获取写权限
writer_acquire() {
    echo "wait" > "$WRITER_FIFO"
    read response < "$WRITER_FIFO"
}
# 写者释放写权限
writer_release() {
    echo "done" > "$WRITER_FIFO"
}

使用临时文件实现计数信号量

计数信号量实现

#!/bin/bash
SEM_DIR="/tmp/counting_sem"
SEM_COUNT=3  # 最大并发数
# 初始化
init_semaphore() {
    mkdir -p "$SEM_DIR"
    # 创建初始令牌
    for i in $(seq 1 $SEM_COUNT); do
        touch "$SEM_DIR/token_$i"
    done
}
# P操作 - 获取一个令牌
P() {
    while true; do
        # 查找并获取一个令牌
        for token in "$SEM_DIR"/token_*; do
            if [ -f "$token" ]; then
                if rm "$token" 2>/dev/null; then
                    echo "获取令牌: $token"
                    return 0
                fi
            fi
        done
        sleep 1
    done
}
# V操作 - 释放一个令牌
V() {
    local token="$SEM_DIR/token_$$_$(date +%s%N)"
    touch "$token"
    echo "释放令牌"
}
# 清理
cleanup() {
    rm -rf "$SEM_DIR"
}
# 使用示例
init_semaphore
# 启动多个并发进程
for i in {1..5}; do
    (
        echo "进程 $$ 等待资源"
        P
        echo "进程 $$ 获取资源,处理中..."
        sleep $((RANDOM % 3 + 1))
        V
        echo "进程 $$ 完成"
    ) &
done
wait
cleanup

高级封装示例

完整的信号量库

#!/bin/bash
# semaphore.sh - 信号量库
# 配置
SEM_BASE_DIR="/dev/shm/semaphores"
# 初始化信号量
sem_init() {
    local name=$1
    local count=${2:-1}
    local dir="$SEM_BASE_DIR/$name"
    mkdir -p "$dir"
    echo "$count" > "$dir/max"
    echo "0" > "$dir/current"
    # 创建初始令牌
    for i in $(seq 1 $count); do
        touch "$dir/token_$i"
    done
}
# 获取信号量
sem_wait() {
    local name=$1
    local timeout=${2:-0}
    local dir="$SEM_BASE_DIR/$name"
    local start_time=$(date +%s)
    while true; do
        for token in "$dir"/token_*; do
            if [ -f "$token" ] && rm "$token" 2>/dev/null; then
                local current=$(< "$dir/current")
                echo $((current + 1)) > "$dir/current"
                return 0
            fi
        done
        # 检查超时
        if [ "$timeout" -gt 0 ]; then
            local elapsed=$(( $(date +%s) - start_time ))
            if [ "$elapsed" -ge "$timeout" ]; then
                echo "Timeout waiting for semaphore: $name"
                return 1
            fi
        fi
        sleep 0.1
    done
}
# 释放信号量
sem_post() {
    local name=$1
    local dir="$SEM_BASE_DIR/$name"
    local token="$dir/token_$$_$(date +%s%N)"
    touch "$token"
    local current=$(< "$dir/current")
    echo $((current - 1)) > "$dir/current"
}
# 销毁信号量
sem_destroy() {
    local name=$1
    local dir="$SEM_BASE_DIR/$name"
    rm -rf "$dir"
}
# 信号量状态
sem_status() {
    local name=$1
    local dir="$SEM_BASE_DIR/$name"
    if [ -d "$dir" ]; then
        local max=$(< "$dir/max")
        local current=$(< "$dir/current")
        local available=$(ls "$dir" | grep -c "token_" 2>/dev/null)
        echo "Semaphore '$name':"
        echo "  Max: $max"
        echo "  In Use: $current"
        echo "  Available: $available"
    else
        echo "Semaphore '$name' not found"
    fi
}
# 使用示例
if [ "$1" = "test" ]; then
    # 创建2个资源的信号量
    sem_init "test_sem" 2
    # 启动3个并发任务
    for i in {1..3}; do
        (
            echo "Task $i: Waiting for resource..."
            if sem_wait "test_sem" 5; then
                echo "Task $i: Got resource, working..."
                sleep 2
                echo "Task $i: Done"
                sem_post "test_sem"
            else
                echo "Task $i: Failed to get resource"
            fi
        ) &
    done
    wait
    sem_status "test_sem"
    sem_destroy "test_sem"
fi

使用建议

  1. 选择合适的方法

    • flock:简单可靠,推荐用于大多数场景
    • 文件锁:最通用,但注意竞态条件
    • 命名管道:适合进程间通信
  2. 错误处理

    • 添加超时机制
    • 处理信号中断
    • 确保锁的释放
  3. 性能考虑

    • 避免忙等待,使用延迟重试
    • 使用内存文件系统(/dev/shm)提高性能
  4. 调试技巧

    # 查看当前锁
    lslocks
    # 或使用脚本调试
    set -x

选择合适的同步机制取决于具体需求,flock 通常是实现Shell脚本信号量同步的最简单可靠方式。

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