本文目录导读:

在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
使用建议
-
选择合适的方法:
flock:简单可靠,推荐用于大多数场景- 文件锁:最通用,但注意竞态条件
- 命名管道:适合进程间通信
-
错误处理:
- 添加超时机制
- 处理信号中断
- 确保锁的释放
-
性能考虑:
- 避免忙等待,使用延迟重试
- 使用内存文件系统(/dev/shm)提高性能
-
调试技巧:
# 查看当前锁 lslocks # 或使用脚本调试 set -x
选择合适的同步机制取决于具体需求,flock 通常是实现Shell脚本信号量同步的最简单可靠方式。