讀取所有輸入後關閉 FIFO 的所有讀取器?
讀取所有輸入後,如何關閉 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 執行 a
read()
操作,該 fd 將掛起,因為管道中還沒有任何內容。5 可能是此時要安排的一個程序。那是執行 shell 的內置
echo
命令,因此它只會執行 awrite("adam...)
並終止,這涉及將其 fd 關閉為output.pipe
.然後 1
read()
現在可以繼續,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
Where
todo.pipe
僅打開一次以供閱讀,因此兩者都cat
共享 fd(對於 相同output.pipe
),避免了此類問題,這可能不會很有用。第
cat
一個read()
會吞噬整個echo
’s 的輸出,而不會給另一個留下任何東西。任何即使您將其替換echo
為輸出大於cat
’ 的讀取緩衝區的東西,從而使兩個cat
s 都有機會各自抓取一些片段,但每個片段最終都會以看起來像隨機方式的方式切割。如果您刪除s
cat |
以讓read
s 直接從管道中讀取,則由於內置函式一次讀取一個字節,情況會更糟read
,因此您最終會喜歡兩個相互競爭read
的 s 依次讀取一個字節。這種方法工作的唯一方法是使用
cat
並確保饋送程序的todo.pipe
速度足夠慢,以至於在下一個任務被饋送之前,第一個任務已經被其中一個讀取,一次cat
饋送一個 todo 任務通過一次write()
系統呼叫,任務不大於 4KiB 也不大於cat
的讀取緩衝區大小。最好讓一個程序讀取管道並將任務分派給工作人員,例如使用 GNU
xargs -P
或 GNU之類的東西parallel
。