You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用flock实现的计数器为何会随机重置?

问题:flock实现原子计数器时随机重置的原因分析

尝试用flock避免竞态条件实现计数器,但counter inc未能持续原子递增,计数器会随机重置。代码用于GNU parallel批量处理文件时更新状态行,计数器存储在RAM临时文件中,简化为无限循环测试后问题仍存在。

原代码

#!/bin/bash

clear_line () {
    echo -ne "\r\033[K"
}

counter () {
  {
    flock -s 200
    read numfiles < "$countfile"
    if [ "$1" = "inc" ]; then
        ((numfiles++))
    fi
    if [ "$1" = "rst" ]; then
        numfiles=0
    fi
    if [ "$1" = "get" ]; then
        echo "$numfiles"
    fi
    echo "$numfiles" > "$countfile"
  } 200> "$countlockfile"
}

process () {
    counter inc
    currfileno=$(counter get)

    clear_line
    echo -n "$currfileno"
}

# 临时文件清理逻辑
tmpdir=
cleanup () {
  trap - EXIT
  if [ -n "$tmpdir" ] ; then rm -rf "$tmpdir"; fi
  if [ -n "$1" ]; then trap - $1; kill -$1 $$; fi
}
tmpdir=$(mktemp -dt "$(basename $0).XXXXXXXX" --tmpdir=/run/user/$(id -u)) || exit 1
countfile=$(mktemp -t "counter.XXXXXXXX" --tmpdir=$tmpdir) || exit 1
countlockfile=$(mktemp -t "countlockfile.XXXXXXXX" --tmpdir=$tmpdir) || exit 1

trap 'cleanup' EXIT
trap 'cleanup HUP' HUP
trap 'cleanup TERM' TERM
trap 'cleanup INT' INT

export -f clear_line
export -f process
export -f counter
export countfile
export countlockfile

counter rst
while :
do
    echo whatever
done | parallel process

问题原因

  • 两次独立的counter调用导致竞态:process函数中先执行counter inc再执行counter get,这两步之间没有锁保护。进程A执行完counter inc释放锁后,进程B可能已经修改了计数器值,此时进程A再调用counter get拿到的不是自己递增后的结果;极端情况下,多个进程交错执行inc和get,会导致文件写入覆盖,出现计数器重置。
  • counter get的冗余写入:counter get在读取数值后会将相同值写回文件,虽然数值未变,但多个进程并发执行时会放大竞态窗口,增加出错概率。

修复方案

将递增和获取值合并为一次原子操作,避免两次调用之间的间隙。修改counter函数,让inc操作直接返回递增后的数值:

修改后的counter函数

counter () {
  {
    flock -s 200
    read numfiles < "$countfile"
    local output=""
    if [ "$1" = "inc" ]; then
        ((numfiles++))
        output="$numfiles"
    fi
    if [ "$1" = "rst" ]; then
        numfiles=0
    fi
    if [ "$1" = "get" ]; then
        output="$numfiles"
    fi
    echo "$numfiles" > "$countfile"
    # 仅在需要时输出结果
    [ -n "$output" ] && echo "$output"
  } 200> "$countlockfile"
}

修改后的process函数

process () {
    currfileno=$(counter inc)
    clear_line
    echo -n "$currfileno"
}

这样递增和获取值在同一个锁保护的代码块中完成,彻底消除两次调用之间的竞态条件,保证计数器持续原子递增。

内容的提问来源于stack exchange,提问作者localhost

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:43:21