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

