Shell脚本如何实现时间轮定时器

wen 实用脚本 17

本文目录导读:

Shell脚本如何实现时间轮定时器

  1. 基础时间轮实现
  2. 进阶版:多层时间轮
  3. 实用版本:带回调函数
  4. 特性对比
  5. 使用建议

我来详细介绍如何在Shell脚本中实现时间轮定时器。

基础时间轮实现

简单的时间轮定时器

#!/bin/bash
# 时间轮定时器实现
# 基本结构:时间槽数组 + 指针
# 时间轮配置
WHEEL_SIZE=60  # 时间槽数量(60秒轮转一圈)
TICK_INTERVAL=1  # 每次tick间隔(秒)
# 初始化时间轮
declare -a time_wheel
for ((i=0; i<WHEEL_SIZE; i++)); do
    time_wheel[$i]=""
done
# 当前指针位置
current_position=0
# 添加定时任务
add_timer() {
    local delay=$1  # 延迟时间(秒)
    local command=$2  # 要执行的命令
    # 计算目标槽位置
    local target_slot=$(( (current_position + delay) % WHEEL_SIZE ))
    # 将任务添加到对应槽
    if [ -z "${time_wheel[$target_slot]}" ]; then
        time_wheel[$target_slot]="$command"
    else
        time_wheel[$target_slot]="${time_wheel[$target_slot]}|$command"
    fi
    echo "Timer added: '$command' (slot $target_slot, delay ${delay}s)"
}
# 执行当前槽的任务
execute_slot() {
    local slot=$1
    local tasks=${time_wheel[$slot]}
    if [ -n "$tasks" ]; then
        IFS='|' read -ra task_array <<< "$tasks"
        for task in "${task_array[@]}"; do
            if [ -n "$task" ]; then
                echo "[$(date '+%H:%M:%S')] Executing: $task"
                eval "$task" &
            fi
        done
        time_wheel[$slot]=""  # 清空当前槽
    fi
}
# 时间轮主循环
run_time_wheel() {
    echo "Time wheel started (wheel size: $WHEEL_SIZE)"
    while true; do
        echo "[$(date '+%H:%M:%S')] Tick - Position: $current_position"
        # 执行当前槽的任务
        execute_slot $current_position
        # 移动指针
        current_position=$(( (current_position + 1) % WHEEL_SIZE ))
        # 等待下一个tick
        sleep $TICK_INTERVAL
    done
}
# 使用示例
echo "=== 时间轮定时器示例 ==="
# 添加一些定时任务
add_timer 3 "echo 'Task 1: 3 seconds delay'"
add_timer 5 "echo 'Task 2: 5 seconds delay'"
add_timer 10 "echo 'Task 3: 10 seconds delay'"
add_timer 65 "echo 'Task 4: 65 seconds delay (wrap around)'"
# 启动时间轮(后台运行)
run_time_wheel &
wheel_pid=$!
# 运行一段时间后停止
sleep 15
kill $wheel_pid 2>/dev/null
echo "Time wheel stopped"

进阶版:多层时间轮

#!/bin/bash
# 多层时间轮实现
# 支持更长时间范围的定时器
# 配置参数
declare -A WHEEL_CONFIG
WHEEL_CONFIG[0,size]=60    # 第一层:60个槽,每个槽1秒
WHEEL_CONFIG[0,interval]=1
WHEEL_CONFIG[1,size]=60    # 第二层:60个槽,每个槽1分钟
WHEEL_CONFIG[1,interval]=60
WHEEL_CONFIG[2,size]=24    # 第三层:24个槽,每个槽1小时
WHEEL_CONFIG[2,interval]=3600
LAYERS=3
# 初始化所有层
declare -A time_wheel
init_wheels() {
    for ((layer=0; layer<LAYERS; layer++)); do
        local size=${WHEEL_CONFIG[$layer,size]}
        for ((slot=0; slot<size; slot++)); do
            time_wheel[$layer,$slot]=""
        done
    done
}
# 当前位置指针
declare -a current_positions
for ((i=0; i<LAYERS; i++)); do
    current_positions[$i]=0
done
# 添加定时器到时间轮
add_timer_ml() {
    local delay=$1
    local command=$2
    local layer=0
    local remaining=$delay
    # 找到合适的层级
    for ((layer=0; layer<LAYERS; layer++)); do
        local interval=${WHEEL_CONFIG[$layer,interval]}
        if [ $remaining -lt $(( interval * ${WHEEL_CONFIG[$layer,size]} )) ]; then
            break
        fi
    done
    if [ $layer -ge $LAYERS ]; then
        echo "Error: Delay too large"
        return 1
    fi
    local interval=${WHEEL_CONFIG[$layer,interval]}
    local size=${WHEEL_CONFIG[$layer,size]}
    local pos=${current_positions[$layer]}
    # 计算目标槽
    local target_slot=$(( (pos + remaining / interval) % size ))
    # 存储任务信息
    time_wheel[$layer,$target_slot]="${time_wheel[$layer,$target_slot]}|$layer,$remaining,$command"
    echo "Multi-layer timer added: '$command' (layer $layer, slot $target_slot)"
}
# 执行层级任务
execute_layer() {
    local layer=$1
    local pos=${current_positions[$layer]}
    local size=${WHEEL_CONFIG[$layer,size]}
    local interval=${WHEEL_CONFIG[$layer,interval]}
    local tasks=${time_wheel[$layer,$pos]}
    if [ -n "$tasks" ]; then
        IFS='|' read -ra task_array <<< "$tasks"
        for task_info in "${task_array[@]}"; do
            if [ -n "$task_info" ]; then
                IFS=',' read -r org_layer remaining command <<< "$task_info"
                if [ $remaining -le $interval ]; then
                    # 任务到期,执行
                    echo "[$(date '+%H:%M:%S')] Executing: $command"
                    eval "$command" &
                else
                    # 任务还有剩余时间,下移到更低层级
                    local new_delay=$((remaining - interval))
                    local new_layer=$((layer - 1))
                    if [ $new_layer -ge 0 ]; then
                        local new_interval=${WHEEL_CONFIG[$new_layer,interval]}
                        local new_pos=${current_positions[$new_layer]}
                        local new_slot=$(( (new_pos + new_delay / new_interval) % ${WHEEL_CONFIG[$new_layer,size]} ))
                        time_wheel[$new_layer,$new_slot]="${time_wheel[$new_layer,$new_slot]}|$new_layer,$new_delay,$command"
                    fi
                fi
            fi
        done
        time_wheel[$layer,$pos]=""
    fi
}
# 多层时间轮主循环
run_multi_layer_wheel() {
    init_wheels
    while true; do
        for ((layer=0; layer<LAYERS; layer++)); do
            local interval=${WHEEL_CONFIG[$layer,interval]}
            local size=${WHEEL_CONFIG[$layer,size]}
            # 检查是否需要触发该层
            if [ $(( $(date +%s) % interval )) -eq 0 ]; then
                execute_layer $layer
                current_positions[$layer]=$(( (current_positions[$layer] + 1) % size ))
            fi
        done
        sleep 1
    done
}

实用版本:带回调函数

#!/bin/bash
# 实用时间轮实现
# 定时器条目结构:ID|执行时间|命令|重复次数
declare -A timer_entries
timer_counter=0
# 时间轮配置
WHEEL_SIZE=100
TICK_MS=100  # 100ms tick
# 初始化
declare -a wheel
for ((i=0; i<WHEEL_SIZE; i++)); do
    wheel[$i]=""
done
current_slot=0
# 注册定时器
register_timer() {
    local delay_ms=$1
    local command=$2
    local repeat=${3:-1}  # 默认执行1次,-1表示无限重复
    local timer_id=$((timer_counter++))
    local target_slot=$(( (current_slot + delay_ms / TICK_MS) % WHEEL_SIZE ))
    local remaining_ticks=$(( delay_ms / TICK_MS ))
    # 存储定时器信息
    local entry="$timer_id|$remaining_ticks|$command|$repeat"
    if [ -z "${wheel[$target_slot]}" ]; then
        wheel[$target_slot]="$entry"
    else
        wheel[$target_slot]="${wheel[$target_slot]},$entry"
    fi
    echo "Timer #$timer_id registered: '$command' (${delay_ms}ms, $repeat times)"
    return $timer_id
}
# 取消定时器
cancel_timer() {
    local timer_id=$1
    for ((i=0; i<WHEEL_SIZE; i++)); do
        if [ -n "${wheel[$i]}" ]; then
            IFS=',' read -ra entries <<< "${wheel[$i]}"
            local filtered=""
            for entry in "${entries[@]}"; do
                local id=$(echo "$entry" | cut -d'|' -f1)
                if [ "$id" != "$timer_id" ]; then
                    if [ -z "$filtered" ]; then
                        filtered="$entry"
                    else
                        filtered="$filtered,$entry"
                    fi
                fi
            done
            wheel[$i]="$filtered"
        fi
    done
    echo "Timer #$timer_id cancelled"
}
# 执行定时器
execute_timer() {
    local entry=$1
    IFS='|' read -r timer_id remaining_ticks command repeat <<< "$entry"
    if [ $remaining_ticks -le 0 ]; then
        # 执行命令
        echo "[$(date '+%H:%M:%S.%N')] Executing #$timer_id: $command"
        eval "$command" &
        # 检查是否需要重复
        if [ $repeat -gt 1 ] || [ $repeat -eq -1 ]; then
            # 重新注册
            if [ $repeat -gt 1 ]; then
                register_timer $(($TICK_MS * ${WHEEL_SIZE:-100})) "$command" $((repeat - 1))
            else
                register_timer $(($TICK_MS * ${WHEEL_SIZE:-100})) "$command" -1
            fi
        fi
    else
        # 剩余时间减少,移到合适的位置
        local new_remaining=$((remaining_ticks - 1))
        local new_slot=$(( (current_slot + new_remaining) % WHEEL_SIZE ))
        local new_entry="$timer_id|$new_remaining|$command|$repeat"
        if [ -z "${wheel[$new_slot]}" ]; then
            wheel[$new_slot]="$new_entry"
        else
            wheel[$new_slot]="${wheel[$new_slot]},$new_entry"
        fi
    fi
}
# 时间轮主循环
start_wheel() {
    echo "Time wheel started (${TICK_MS}ms per tick)"
    while true; do
        # 处理当前槽
        local entries=${wheel[$current_slot]}
        if [ -n "$entries" ]; then
            IFS=',' read -ra entry_list <<< "$entries"
            for entry in "${entry_list[@]}"; do
                if [ -n "$entry" ]; then
                    execute_timer "$entry"
                fi
            done
        fi
        # 清空当前槽
        wheel[$current_slot]=""
        # 移动指针
        current_slot=$(( (current_slot + 1) % WHEEL_SIZE ))
        # 等待
        sleep $(echo "scale=3; $TICK_MS/1000" | bc)
    done
}
# 使用示例
echo "=== 实用时间轮示例 ==="
# 注册定时器
register_timer 500 "echo 'Fast task (500ms)'"
register_timer 1000 "echo '1 second task'"
register_timer 3000 "echo '3 seconds task'" 2
register_timer 5000 "echo '5 seconds task (repeat)'" -1
# 启动时间轮
start_wheel &
wheel_pid=$!
# 运行一段时间后取消一个定时器
sleep 2
cancel_timer 3
sleep 8
kill $wheel_pid 2>/dev/null
echo "Time wheel stopped"

特性对比

特性 简单时间轮 多层时间轮 实用时间轮
时间精度 1秒 可配置 毫秒级
最大延迟 60秒 24小时+ 可配置
任务重复
任务取消
复杂度

使用建议

  1. 选择合适的粒度:根据需求调整tick间隔
  2. 处理任务超时:添加超时检测机制
  3. 避免累积延迟:使用date命令获取准确时间
  4. 资源管理:及时清理已完成的任务

这些实现可以根据你的具体需求进行调整和扩展。

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