使用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
相关产品推荐
相关产品推荐

