并行进程完成后因输出重定向导致wait永久挂起问题
问题原因与解决方案
一、为什么脚本会在wait处永久挂起?
这是进程替换(>(...))与wait命令的死锁问题,具体逻辑如下:
- 你用
2> >(xerr)和> >(xlog)做输出重定向时,bash会创建两个独立的子进程(执行xerr和xlog里的sed命令),这两个进程会通过管道接收大括号{}块的输出/错误。 wait命令默认会等待当前shell的所有子进程结束——包括你用&启动的sleep/echo后台进程,以及进程替换创建的sed子进程。- 当所有sleep/echo后台进程都执行完毕并输出内容后,sed进程仍会保持运行:因为它们的标准输入(管道读端)还没收到EOF,而管道的写端被大括号块的进程持有(大括号进程此时正卡在
wait)。 - 死锁形成:大括号进程在
wait等待sed进程结束,sed进程在等大括号进程关闭管道写端(发送EOF),双方互相等待,导致脚本永久挂起。
而两种正常情况的原因:
- 情况1(无并行):所有命令前台执行,大括号块执行完毕后自动关闭管道写端,sed进程收到EOF后退出,无死锁。
- 情况2(无重定向):没有创建sed子进程,
wait只需等待sleep/echo后台进程,全部结束后脚本正常推进。
二、如何同时实现并行执行与输出重定向?
提供两种可靠的解决方法:
方法1:显式指定wait等待的进程PID
只让wait等待你手动启动的sleep/echo后台进程,忽略进程替换创建的sed进程:
#!/bin/bash xerr() { sed -ue "s/^/(stderr) ${FUNCNAME[1]}: /" >&2 } xlog() { sed -ue "s/^/(stdout) ${FUNCNAME[1]}: /" } main() { { # 保存每个后台进程的PID sleep 0.25 && echo "ok" & pid1=$! sleep 1.00 && echo "ok" & pid2=$! sleep 0.65 && echo "fail" >&2 & pid3=$! sleep 1.00 && echo "ok" & pid4=$! sleep 0.65 && echo "ok" & pid5=$! # 只等待这些指定的PID wait $pid1 $pid2 $pid3 $pid4 $pid5 echo "Finished" } 2> >(xerr) > >(xlog) } main
方法2:用子shell包裹并行进程
将所有后台进程放到子shell中执行,子shell结束后会自动关闭管道写端,让sed进程收到EOF并退出:
#!/bin/bash xerr() { sed -ue "s/^/(stderr) ${FUNCNAME[1]}: /" >&2 } xlog() { sed -ue "s/^/(stdout) ${FUNCNAME[1]}: /" } main() { # 把并行逻辑放到子shell(小括号)里,而非大括号 ( sleep 0.25 && echo "ok" & sleep 1.00 && echo "ok" & sleep 0.65 && echo "fail" >&2 & sleep 1.00 && echo "ok" & sleep 0.65 && echo "ok" & wait echo "Finished" ) 2> >(xerr) > >(xlog) } main
执行这两种修改后的脚本,都会正常输出(stdout) main: Finished,且不会挂起。
内容的提问来源于stack exchange,提问作者LittleFoxyFox
相关产品推荐
相关产品推荐

