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

Bourne Shell下用flock锁实现多脚本并发安全读写文件

现有实现的问题
  • 锁逻辑完全失效:你代码里的2> sensor1lockfile是将标准错误输出重定向到锁文件,并没有将文件描述符2以读写模式绑定到锁文件供flock调用,这也是你之前尝试直接用数据文件替换锁文件运行失败的核心原因——flock根本没有拿到可加锁的打开文件句柄,加锁操作完全没生效,两个脚本会出现竞态覆盖数据。
  • 子shell性能损耗:你用()包裹逻辑块,每次执行都会fork新的子进程,写入脚本是无限循环高频运行,长期跑会产生不必要的CPU开销。
  • 清空操作存在竞态:现有逻辑先执行awk读取文件,再单独执行截断清空,一旦锁提前释放,写入脚本在两个操作间隙写入的数据会被直接清空丢失。
  • 文件描述符使用不合理:固定占用fd2做锁操作,既容易和标准错误输出冲突,扩展到10个传感器时如果固定占用多个fd,确实会碰到fd编号不能超过9的限制。
优化方案

核心思路

不需要单独维护锁文件:flock是基于打开的文件句柄加建议锁,只要以读写模式打开数据文件本身,就可以直接对数据文件加锁,省掉额外的锁文件开销。
避免子shell:直接将需要加锁执行的命令跟在flock后面,不需要用()包裹代码块,不会fork子进程,性能更高。
绕开fd数量限制:要么复用单个固定fd循环处理所有传感器文件,要么用POSIX标准支持的动态fd分配,由shell自动管理可用fd,不需要手动指定编号,完全不受fd<=9的限制。

写入脚本示例(单传感器,可批量复制扩展)

动态fd版本(推荐):

# 提前以读写模式打开传感器数据文件,shell自动分配可用fd,长期持有不需要反复打开
exec {sensor_fd}<>sensor1.txt

while :; do
    # 采集传感器数据的逻辑放这里,替换成你实际的采样命令
    SENSOR_1_VALUE=$(cat /sys/class/hwmon/hwmon0/temp1_input)

    # 加排他锁,写入数据后自动解锁
    flock -x "$sensor_fd" echo "$SENSOR_1_VALUE" >&"$sensor_fd"

    # 按你实际需要的采样间隔调整sleep时间
    sleep 0.2
done

如果运行环境是非常老的不支持动态fd的sh版本,用固定fd9的兼容写法:

exec 9<>sensor1.txt
while :; do
    SENSOR_1_VALUE=$(cat /sys/class/hwmon/hwmon0/temp1_input)
    flock -x 9 echo "$SENSOR_1_VALUE" >&9
    sleep 0.2
done

计算脚本示例(支持10个传感器,每秒执行一次)

动态fd版本(推荐,性能更好):

#!/bin/sh
while :; do
    for i in $(seq 1 10); do
        sensor_file="sensor${i}.txt"
        # 不存在则创建文件,避免打开失败
        touch "$sensor_file"
        # 自动分配fd打开文件
        exec {fd}<>"$sensor_file"
        # 加锁后完成读取计算、截断清空,执行完自动释放锁
        flock -x "$fd" awk -v sid="$i" '
            { total += $1; count++ }
            END {
                if (count > 0) {
                    printf "sensor%d average: %.2f\n", sid, total/count
                } else {
                    printf "sensor%d average: no data in last second\n", sid
                }
            }
        ' "$sensor_file"
        # 直接通过持锁的fd截断文件,不会产生竞态
        : >&"$fd"
        # 关闭fd释放资源
        exec {fd}>&-
    done

    # 此处添加将平均值发送给其他子系统的逻辑
    sleep 1
done

固定fd9兼容版本(全程只占用1个fd,完全符合fd<=9的要求):

#!/bin/sh
while :; do
    for i in $(seq 1 10); do
        sensor_file="sensor${i}.txt"
        touch "$sensor_file"
        # 复用fd9打开当前传感器文件
        exec 9<>"$sensor_file"
        flock -x 9 awk -v sid="$i" '
            { total += $1; count++ }
            END {
                if (count > 0) {
                    printf "sensor%d average: %.2f\n", sid, total/count
                } else {
                    printf "sensor%d average: no data in last second\n", sid
                }
            }
        ' "$sensor_file"
        : >&9
        # 关闭fd9,供下一个传感器文件复用
        exec 9>&-
    done

    # 发送结果到其他子系统的逻辑
    sleep 1
done
关键说明
  • 动态fd语法exec {fd}<>file是POSIX sh标准支持的特性,所有主流的Bourne shell实现(dash、busybox sh、bash等)都支持,shell自动分配的fd号即使大于9也不会和内置预留的0-9号fd冲突,不需要担心兼容性问题。
  • 截断文件时通过已经持有锁的fd执行: >&$fd,比重新通过路径打开文件再截断更安全,全程锁不释放,完全不会丢数据。
  • 写入脚本长期持有打开的文件句柄,不需要每次循环都重新打开文件,能大幅减少高频写入场景下的IO开销。
  • 扩展到10个传感器时总共只需要维护10个数据文件,不需要额外的锁文件,满足减少文件数量的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 17:36:18