并发写日志错位因bash重定向非原子,应使用flock、分文件或syslog;后台进程需显式wait防丢失;管道中变量修改不回传,应避免无意义管道。

bash & 并发导致文件写入混乱
多个子进程同时往同一个文件用 echo "log" >> file.log 追加,结果出现内容错位、半截行、甚至字节级覆盖——这不是概率问题,是 POSIX shell 重定向的固有行为。系统调用 open(..., O_APPEND) 保证每次 write() 原子追加,但 >> 在 bash 里先 open 再 lseek 到末尾再 write,中间有竞态窗口。
实操建议:
- 用
flock包裹写操作:flock /tmp/log.lock -c 'echo "$(date): $MSG" >> /var/log/myapp.log' - 避免所有并发进程共用一个日志文件;改用按 PID 或时间戳分文件,如
/tmp/log.$$.txt - 若必须集中日志,交给
syslog或logger:它底层用 Unix domain socket,天然串行化
wait 丢失导致父脚本提前退出
写了 cmd1 & cmd2 &,但没跟 wait,脚本执行完就退出,后台进程变成孤儿、被 init 收养,后续无法捕获退出码或信号。
实操建议:
- 显式
wait是必须的,不是可选优化:cmd1 & p1=$!; cmd2 & p2=$!; wait $p1 $p2 - 需要等全部后台任务?直接
wait不带参数即可,它会等当前 shell 启动的所有子进程 - 注意
set -e下,某个后台命令失败不会触发退出——wait才是检查点,务必在wait后判断$?
变量在子 shell 中修改不回传
for i in {1..3}; do echo $i; done | while read n; do count=$((count+1)); done ——最后 $count 还是空。管道每个段都运行在独立子 shell,变量修改只在子 shell 生效。
实操建议:
- 避免无意义的管道:改用
while read n; do ...; done - 真要并行处理数据流?用命名管道(
mkfifo)或临时文件中转,别依赖变量跨进程传递 - 如果只是计数,优先用
wc -l这类外部命令,而不是在 shell 循环里累加
并发数失控拖垮系统
写了个 for f in *.log; do process_log "$f" & done,目录有 5000 个文件,瞬间拉起 5000 个进程,内存爆满、IO 阻塞、调度失衡。
实操建议:
- 用
parallel控制并发度:ls *.log | parallel -j4 process_log,-j4表示最多 4 个并发 - 不用
parallel?手写信号量:用mkdir的原子性模拟锁,配合while [ $(ls /tmp/sem.* | wc -l) -ge 4 ]; do sleep 0.1; done - 注意
ulimit -u限制用户进程数,别让脚本突破系统级防护
真正麻烦的不是“怎么并发”,而是“怎么安全地收住并发”。资源边界、状态可见性、错误传播路径——这些在单进程里不显眼,一并发就全暴露出来。










