この問題を説明する方法がわからないので、次の例を使用します。
#!/bin/bash
cleanup() {
rm "$myfifo"
rm "$mylock"
kill '$(jobs -p)'
}
writer() {
for i in $(seq 0 100); do
echo "$(date -R) writing \"$i\"."
echo "$i" > "$myfifo"
done
}
reader() {
while true; do
flock 3
read -st 1 line
status=$?
if [ $status -eq 0 ]; then
echo "$(date -R) reading \"$line\" in thread $1."
else
echo "$(date -R) status $status in thread $1.
break
fi
flock -u 3
sleep 10
done 3<"$mylock" <"$myfifo"
}
trap cleanup EXIT
myfifo="$(mktemp)"
mylock="$(mktemp)"
rm "$myfifo"
mkfifo "$myfifo"
writer &
for i in $(seq 1 10); do
reader $i &
sleep 1
done
wait
これで、読み取りスレッドはそれぞれ1行(または数行)を読み取りたいと思いますが、最初の読み取りプロセスはすべての行を読み取ります(ランダムな順序で理解していないが重要ではありません)。バッファのどこかにあり、他のすべての読み取りプロセスは行を取得しません。
また、Reader 2-10は終了しないため、Readコマンドに指定されたタイムアウトパラメータが機能しないようです。
- なぜ?
- 行が読者間で(ある程度)均等に分配されるように、この問題をどのように解決できますか?
答え1
read
タイムアウトを許可
read
タイムアウトが機能します。ここでの問題は、FIFOが書き込みモードで開かれるまで、読み取りモードでFIFOを開くことがブロックされることです。この場合、read
ブロックするのではなく、bash
FIFOがstdinにリダイレクトされている間はブロックします。
他のプロセスが書き込み用にFIFOを開くと、読み取り用にbash
FIFOが正常に開き、read
コマンドが実行されます(予想どおりにタイムアウトしました)。
Linuxを使用している場合は、fifo のマニュアルページ「読み取りと書き込みのためにFIFOを開くことは、ブロックモードと非ブロックモードの両方で成功します」と伝えます。したがって、他のプロセスに書き込むために開いているFIFOがない場合でも、次のコマンドはタイムアウトします。
read -st 1 data <> "$fifo"
競争条件に注意してください。
シェルプロセスが読み取り用にFIFOを開くと、作成者のロックが解除され、FIFOがstdinにリダイレクトされて呼び出されるbash
と、作成者read
はFIFOを開き、ここに書き込むことができます。何度も。一度に 1 行しか読み取ることができないため、FIFO の両端を閉じると、読み取る残りの行が失われます。より良い解決策は、fd 3で行ったように、while
ループ全体でstdinにリダイレクトしてFIFOを開いたままにすることです。done
次のように:
while ...; do
...
read -st 1 data
...
done 3<"$lock" < "$fifo"
あるいは、並列リーダーが複数ある場合も同様です。 FIFOを開いたままにすることが重要です。作家たちも同じだ。
たとえば、アップデートで公開したコードを使用すると、上位レイヤは次のようになります。
# Writer
writer > "$myfifo" &
# Reader
for i in $(seq 1 10); do
reader $i &
sleep 1
done < "$myfifo"
もちろん、$myfifo
コードの他の場所からリダイレクトを削除し、ビルダーからリダイレクトを削除するか、echo "$(date -R) writing \"$i\"."
stderrにリダイレクトします。それ以外の場合はFIFOに移動します。