Pipe

讀取所有輸入後關閉 FIFO 的所有讀取器?

  • February 8, 2022

讀取所有輸入後,如何關閉 FIFO 的所有讀取器?似乎我只能關閉其中一個,這使我的程序無法完成。

這是一個有效的範常式序(測試將其放入文件中):

set -euo pipefail

rm -f todo.pipe
mkfifo todo.pipe

rm -f output.pipe
mkfifo output.pipe

cat todo.pipe | \
   while read line && echo hej $line; do :; done \
           > output.pipe &

echo "adam\n bertil\n carl" > todo.pipe &

cat < output.pipe

正如預期的那樣,輸出是:

❯ ./test.zsh
hej adam
hej bertil
hej carl
❯

但是,如果我添加另一個執行緒來處理這些事情todo.pipe,事情就會永遠掛起:

set -euo pipefail

rm -f todo.pipe
mkfifo todo.pipe

rm -f output.pipe
mkfifo output.pipe

cat todo.pipe | \
   while read line && echo hej $line; do :; done \
           > output.pipe &
# The below 3 lines is all that's changed
cat todo.pipe | \
   while read line && echo hej $line; do :; done \
           > output.pipe &

echo "adam\n bertil\n carl" > todo.pipe &

cat < output.pipe

現在,它的列印結果和以前一樣,但它永遠不會返回。為什麼?我怎樣才能解決這個問題?

我的懷疑是第二個“工作執行緒”現在得到一個 EOF 或類似的東西,但感覺就像我在這裡遺漏了一些基本的東西。

重要的是要意識到以讀取模式打開(而不是讀取)FIFO 會阻塞塊,直到某個其他程序也以寫入模式打開它(反之亦然),並且一旦發生這種情況,管道就會被實例化。

然後,更多程序可以通過在該管道處於活動狀態時打開 fifo 來連接到該管道。

一旦沒有從任何程序中打開的 fd,該管道就會被銷毀,之後我們回到方形,只要再次打開 fifo 進行讀取和寫入,就可以實例化另一個管道。

在:

[0]
[1] cat todo.pipe |
    [2] while read line && echo hej $line; do :; done \
            > output.pipe &
# The below 3 lines is all that's changed
[3] cat todo.pipe |
    [4] while read line && echo hej $line; do :; done \
            > output.pipe &

[5] echo "adam\n bertil\n carl" > todo.pipe &

[6] cat < output.pipe

主 shell 程序將同時生成 4 個程序,每個程序獨立且並行地過著自己的生活,第一個執行第一個管道,第二個執行第二個管道,第三個執行echo,第四個執行cat(打開後output.pipe)。

管道也將產生一個額外的程序來執行cat todo.pipe,而原始程序將同時進行while循環。

因此,您將有 6 個(如果您算上等待最後一個的主 shell 程序,則為 7 個cat)大部分同時啟動。我在上面用[1]..標記了它們[6]

如何安排它們取決於系統的程序調度程序。像執行外部命令一樣cat需要時間,shell 本身執行的操作可能會首先發生。

所有的 2、4、5、6 都是從在 shell 中打開一個 fifo 文件開始的。2 和 4 開放output.pipe用於書寫和6閱讀。它們很快就會相互解鎖,並且將實例化管道。

5 將掛起它的只寫打開todo.pipe等待至少一個cat程序以只讀方式打開它。

然後 1 和 3 將為此而競爭。執行cat涉及執行/bin/cat,其中涉及擦除程序記憶體,從磁碟載入執行檔,共享庫,動態連結,執行動態連結,並最終執行其中的程式碼,其中cat將解析其命令行並最終打開該 fifo 文件。

只要 1、3 之一打開 fifo(假設這裡是 1),5 就會被解鎖。1 將繼續對該 fd 執行 aread()操作,該 fd 將掛起,因為管道中還沒有任何內容。

5 可能是此時要安排的一個程序。那是執行 shell 的內置echo命令,因此它只會執行 awrite("adam...)並終止,這涉及將其 fd 關閉為output.pipe.

然後 1read()現在可以繼續,cat按大塊讀取,並將整個小輸出吞噬read()並完成,這涉及將其 fd 關閉到管道的寫入端。

如果此時 3 還沒有打開 fifo,則管道被銷毀,當 3 最終打開 fifo 時,它將掛起,直到其他東西以寫入模式打開 fifo 並實例化一個新的不相關管道發生在這裡。

如果不是因為output.pipe首先打開的事實,那麼該 fifo 也可能會遇到同樣的問題。

現在,即使你成功了:

{
  cat | while...done &
  cat | while...done
} < todo.pipe > output.pipe &
echo ... > todo.pipe &
cat < output.pipe

Wheretodo.pipe僅打開一次以供閱讀,因此兩者都cat共享 fd(對於 相同output.pipe),避免了此類問題,這可能不會很有用。

cat一個read()會吞噬整個echo’s 的輸出,而不會給另一個留下任何東西。任何即使您將其替換echo為輸出大於cat’ 的讀取緩衝區的東西,從而使兩個cats 都有機會各自抓取一些片段,但每個片段最終都會以看起來像隨機方式的方式切割。

如果您刪除scat |以讓reads 直接從管道中讀取,則由於內置函式一次讀取一個字節,情況會更糟read,因此您最終會喜歡兩個相互競爭read的 s 依次讀取一個字節。

這種方法工作的唯一方法是使用cat並確保饋送程序的todo.pipe速度足夠慢,以至於在下一個任務被饋送之前,第一個任務已經被其中一個讀取,一次cat饋送一個 todo 任務通過一次write()系統呼叫,任務不大於 4KiB 也不大於cat的讀取緩衝區大小。

最好讓一個程序讀取管道並將任務分派給工作人員,例如使用 GNUxargs -P或 GNU之類的東西parallel

引用自:https://unix.stackexchange.com/questions/689636