FIFO에 대한 모든 입력을 읽었으면 FIFO의 모든 판독기를 닫으시겠습니까?

FIFO에 대한 모든 입력을 읽었으면 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 또는 이와 유사한 것을 얻고 있다고 의심하지만 여기서는 근본적인 것을 놓치고 있는 것처럼 느껴집니다.

답변1

FIFO 블록을 읽기 모드에서 읽는 것이 아니라 열면 다른 프로세스도 쓰기 모드에서 열 때까지(또는 그 반대로) 차단되며, 그런 일이 발생하면 파이프가 인스턴스화된다는 점을 인식하는 것이 중요합니다.

파이프가 활성화된 동안 FIFO를 열어 더 많은 프로세스를 파이프에 연결할 수 있습니다.

어떤 프로세스에서도 파이프가 열리지 않으면 파이프가 파괴되고 그 후 첫 번째 당사자로 돌아가고 읽기 및 쓰기를 위해 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

메인 셸 프로세스는 동시에 4개의 프로세스를 생성하며, 각 프로세스는 독립적으로 병렬로 실행됩니다. 첫 번째는 첫 번째 파이프를 실행하고, 두 번째는 두 번째 파이프를 실행하고, 세 번째는 실행하고 echo, 네 번째는 cat(열린 후 output.pipe) 실행합니다.

cat todo.pipe파이프 프로세스는 또한 원래 프로세스가 동시에 루프되는 동안 실행할 추가 프로세스를 생성합니다 while.

따라서 6개(마지막 프로세스를 기다리는 메인 쉘 프로세스를 계산하면 7개 cat)가 대부분 동시에 시작됩니다. 나는 [1]그것들을 위에 ..로 표시했습니다 [6].

예약 방법은 시스템의 프로세스 스케줄러에 따라 다릅니다. 외부 명령(예: cat시간이 걸리는 명령)을 실행하려면 셸 자체에서 수행되는 작업이 먼저 발생할 수 있습니다.

2, 4, 5, 6 모두 셸에서 fifo 파일을 열어 시작합니다. 2와 4는 output.pipe쓰기와 6읽기를 위해 열려 있습니다. 곧 서로의 잠금이 해제되고 파이프가 인스턴스화됩니다.

todo.pipe5는 적어도 하나의 cat프로세스가 읽기 전용으로 열릴 때까지 쓰기 전용 열린 상태를 일시 중지합니다 .

그러면 1과 3이 이 점을 놓고 경쟁하게 됩니다. 실행에는 프로세스 메모리 지우기, 디스크에서 실행 파일 로드, 라이브러리 공유, 동적으로 연결, 동적 연결 수행, 마지막으로 해당 명령줄이 구문 분석되고 fifo 파일이 최종적으로 그 안에 있는 코드 실행이 cat포함 됩니다. 열릴./bin/catcat

1 또는 3 중 하나가 FIFO를 열면(여기서는 1이라고 가정) 5개의 잠금이 해제됩니다. 1은 이 fd에 대해 작업을 계속 read()수행할 것인데, 아직 파이프에 아무것도 없기 때문에 정지될 것입니다.

5번은 현재 예정된 프로세스일 수도 있습니다. 이것은 echo쉘의 내장 명령을 실행하고 있으므로 a를 실행 write("adam...)하고 종료하며 fd가 그것을 닫습니다 output.pipe.

그런 다음 read()이제 계속 진행하여 cat큰 덩어리를 읽고 작은 출력 전체를 삼켜 read()파이프의 쓰기 끝 부분까지 fd'ing하는 작업을 포함하여 끝낼 수 있습니다.

3이 그때까지 fifo를 열지 않으면 파이프가 파괴되고 3이 마침내 fifo를 열면 다른 것이 쓰기 모드에서 fifo를 열고 여기에서 배관이 발생하지 않는 관련 없는 새 파이프를 인스턴스화할 때까지 정지됩니다. .

먼저 열리지 않았다면 output.pipefifo와 같은 문제가 발생할 수 있습니다.

이제 그렇게 하더라도:

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

where 는 todo.pipe읽기 위해 한 번만 열리므로 둘 다 catfd를 공유하므로( 와 동일 output.pipe) 이러한 문제를 피하므로 아마도 그다지 유용하지 않을 것입니다.

cat첫 번째 작업을 수행하는 작업은 read()전체 echo출력을 삼키고 다른 작업에는 아무 작업도 수행하지 않습니다. echo읽기 버퍼보다 ​​큰 출력으로 대체하여 cat두 버퍼 모두에 각각 일부 조각을 잡을 수 있는 기회를 제공 하더라도 cat각 조각은 겉보기에 무작위 방식으로 잘려지게 됩니다.

내장 함수가 한 번에 한 바이트씩 읽기 때문에 s가 파이프에서 직접 읽도록 cat |s 를 제거하면 상황은 더욱 악화됩니다. 따라서 경쟁하는 두 개의 s가 순서대로 1바이트를 읽게 됩니다.readreadread

이것이 작동하는 유일한 방법은 다음 작업이 입력되기 전에 첫 번째 작업이 그 중 하나에 의해 읽힐 수 있도록 cat프로세스가 충분히 느린지 확인 하고 시스템 호출을 통해 한 번에 하나의 할일 항목을 입력하는 것입니다 . 4KiB보다 크지 않은 작업의 경우 읽기 버퍼 크기 보다 크지도 않습니다 .todo.pipecatwrite()cat

더 나은 접근 방식은 하나의 프로세스가 파이프를 읽고 xargs -PGNU 또는 GNU와 같은 것을 사용하여 작업을 작업자에게 전달하도록 하는 것입니다 parallel.

관련 정보