我在bash
程序中遇到了奇怪的比赛情况。我尝试通过一个足够简单的演示程序来复制它,但是显然,对于所有/大多数与计时相关的比赛演示尝试,都是如此。
这是程序的抽象版本,不重复该问题,但让我仍然说明:
# Abstracted version of the original program
# that is NOT able to demo the race.
#
function foo() {
local instance=$1
# [A lot of logic here -
# all foreground commands, nothing in the background.]
echo "$instance: test" > /tmp/foo.$instance.log
echo "Instance $instance ended"
}
# Launch the process in background...
#
echo "Launching instance 1"
foo 1 &
# ... and wait for it to complete.
#
echo "Waiting..."
wait
echo "Waiting... done. (wait exited with: $?)"
# This ls command ALWAYS fails in the real
# program in the 1st while-iteration, complaining about
# missing files, but works in the 2nd iteration!
#
# It always works in the very 1st while-iteration of the
# abstracted version.
#
while ! ls -l /tmp/foo.*; do
:
done
在我的原始程序中(而不是上面的抽象版本中),我确实在stdout上看到Waiting... done. (wait exited with: 0)
,就像在上面的版本中看到的那样。然而,ls -l
总是会在原始版本中失败,但始终会在第一个while
循环迭代中以上述抽象版本运行。
此外,尽管在标准输出上看到ls
消息,但Instance 1 ended
命令仍然失败。输出为:
$ ./myProgram
Launching instance 1
Waiting...
Waiting... done. (wait exited with: 0)
Instance 1 ended
ls: cannot access '/tmp/foo.*': No such file or directory
/tmp/foo.1
$
我注意到,如果在原始程序中将sleep 1
放在ls
的前面,就可以安全地消除while循环,就像这样:
# This too works in the original program:
sleep 1
ls -l /tmp/foo.*
问题:为什么wait
不能在我的原始程序中按预期工作?有什么建议至少可以帮助您解决问题吗?
我在Ubuntu 18.04上使用bash 4.4.19
。
编辑:我还验证了失败的原始程序中对wait
的调用正在退出,并且状态码为0
。
编辑2: Instance 1 ended
之前是否不会出现Waiting... done. (wait exited with: 0)
消息?在bash中处理后台进程时,这可能是OS的磁盘缓冲区/缓存的“刷新问题”吗?
编辑3:如果我不是发出while
循环或sleep 1
骇客,而是发出sync
命令,那么,它起作用了!但是,为什么我必须在一个程序中而不是另一个程序中做一个sync
?
答案 0 :(得分:0)
我注意到以下三个骇客都能正常工作,但并不确定原因:
Hack 1
while ! ls -l /tmp/foo.*; do
:
done
Hack 2
sleep 1
ls -l /tmp/foo.*
Hack 3
sync
ls -l /tmp/foo.*
这是否是OS的磁盘缓冲区/缓存的“刷新问题”,尤其是在处理后台进程时,尤其是在bash
中?换句话说,对wait
的调用似乎在刷新磁盘缓存之前返回(或者,在操作系统本身意识到并完成刷新磁盘缓存之前)。
编辑感谢@Jon,他的猜测非常接近,让我朝着正确的方向进行思考,以及@chepner提供的古老的,细微的调整建议。
真正的问题:我开始foo
时,不是直接/简单地(如原始问题中不准确的抽象版本所示),而是通过另一个launchThread
函数,做完簿记后,在其正文中还会说foo 1 &
。并且对launchThread
的呼叫本身带有一个&
后缀!因此,我的wait
确实在等待launchThread
而不是在foo
上! sleep
,sync
和while
只是在帮助争取更多的时间来完成foo
,这就是引入它们的原因。以下是对问题的更准确的说明,即使您可能会也可能无法在自己的系统上复制它(由于系统之间的调度/定时差异):
#!/bin/bash -u
function now() {
date +'%Y-%m-%d %H:%M:%S'
}
function log() {
echo "$(now) - $@" >> $logDir/log # Line 1
}
function foo() {
local msg=$1
log "$msg"
echo " foo ended"
}
function launchThread() {
local f=$1
shift
"$f" "$@" & # Line 2
}
logDir=/tmp/log
/bin/rm -rf "$logDir"
mkdir -p "$logDir"
echo "Launching foo..."
launchThread foo 'message abc' & # Line 3
echo "Waiting for foo to finish..."
wait
echo "Waiting for foo to finish... done. (wait exited with: $?)"
ls "$logDir"/log*
上述越野车程序的输出:
Launching foo...
Waiting for foo to finish...
Waiting for foo to finish... done. (wait exited with: 0)
foo ended
ls: cannot access '/tmp/log/log*': No such file or directory
如果我从&
或Line 2
中删除Line 3
,程序将正常工作,并输出以下内容:
Launching foo...
Waiting for foo to finish...
foo ended
Waiting for foo to finish... done. (wait exited with: 0)
/tmp/log/log
如果我从$(now)
中删除了Line 1
部分,该程序也可以正常工作。