Linux 명명된 파이프: 생각만큼 선입선출이 아님

Linux 명명된 파이프: 생각만큼 선입선출이 아님

간단히 말해서:

mkfifo fifo; (echo a > fifo) &; (echo b > fifo) &; cat fifo

내가 기대한 것:

a
b

첫 번째 프로세스는 echo … > fifo파일을 여는 첫 번째 프로세스여야 하므로 해당 프로세스가 파일에 쓰는 첫 번째 프로세스가 되기를 원합니다(먼저 열고 잠금 해제).

내가 얻는 것:

b
a

놀랍게도 이 동작은 완전히 별도의 프로세스에 쓰기 위해 두 개의 별도 터미널을 열 때도 발생합니다.

명명된 파이프의 선입선출 의미를 잘못 이해하고 있는 걸까요?

Stephen은 지연 추가를 제안했습니다.

#!/usr/bin/zsh
delay=$1
N=$(( $2 - 1 ))
out=$(for n in {00..$N}; do
  mkfifo /tmp/fifo$n
  (echo $n > /tmp/fifo$n) &
  sleep $delay
  (echo $(( $n + 1000 )) > /tmp/fifo$n )&
  # intentionally using `cat` here to not step into any smartness
  cat /tmp/fifo$n | sort -C || echo +1
  rm /tmp/fifo$n
done)
echo "$(( $res )) inverted out of $(( $N + 1 ))"

이제 이 말이 100% 맞습니다( delay = 0.1, N = 100).

여전히 mkfifo fifo; (echo a > fifo) &; sleep 0.1 ; (echo b > fifo) &; cat fifo수동으로 실행거의 언제나역순을 생성합니다.

실제로 복사 및 붙여넣기 for루프 자체도 절반 정도 실패합니다. 그래요매우여기서 무슨 일이 일어나고 있는지 혼란스러워요.

답변1

이는 파이프의 FIFO 의미론과 아무 관련이 없으며 어떤 방식으로든 파이프에 대해 아무것도 증명하지 않습니다. 이는 FIFO가 쓰기 및 읽기를 위해 열릴 때까지 차단되므로 읽기를 cat위해 열릴 fifo때까지 아무 일도 일어나지 않는다는 사실과 관련이 있습니다 .

첫 번째가 echo첫 번째여야 하기 때문입니다.

백그라운드에서 프로세스를 시작한다는 것은 해당 프로세스가 실제로 언제 예약되었는지 알 수 없다는 것을 의미합니다.약속은 없어첫 번째 백그라운드 프로세스는 두 번째 백그라운드 프로세스보다 먼저 작업을 완료합니다. 동일하게 적용됩니다프로세스 차단 해제.

백그라운드 프로세스를 계속 사용하면서 두 번째 프로세스를 인위적으로 지연시켜 확률을 높일 수 있습니다.

rm fifo; mkfifo fifo; echo a > fifo & (sleep 0.1; echo b > fifo) & cat fifo

지연 시간이 길어질수록 기회가 커집니다. echo a > fifo열기가 완료될 때까지 차단하고 fifo, cat시작하고 열고, fifo차단을 해제 echo a한 다음 echo b실행합니다.

그러나 여기서 주요 요인은 catFIFO가 켜져 있을 때입니다. 그때까지 쉘 블록은 리디렉션을 설정하려고 시도합니다. 표시되는 출력 순서는 궁극적으로 쓰기 프로세스가 잠금 해제되는 순서에 따라 달라집니다.

먼저 실행하면 다른 결과가 나타납니다 cat.

rm fifo; mkfifo fifo; cat fifo & echo a > fifo & echo b > fifo

이렇게 하면 fifo쓰기 열기가 차단되지 않으므로(여전히 보장할 수 없음) a처음에는 첫 번째 설정보다 빈도가 더 높아집니다. cat사전 실행 완료 도 확인할 수 있습니다 echo b.출력 만 가능합니다 a.

답변2

파이프는 먼저 들어오고 나가는 방식입니다. 문제는 "in"이 발생할 때 오해한다는 것입니다. "in" 이벤트는 다음과 같습니다.글쓰기, 열 수 없습니다.

쓸모없는 구두점을 제거하면 코드는 다음과 같습니다.

echo a > fifo & echo b > fifo &

그러면 명령이 echo a > fifo병렬로 실행됩니다 echo b > fifo. 먼저 들어오는 사람이 먼저 나가지만, 누가 먼저 들어가는지는 거의 동등한 경쟁이다.

a다른 사람이 먼저 읽기를 원한다면 b먼저 쓰기를 준비해야 합니다 b. 이는 echo a > fifo시작하기 전에 작업이 완료될 때까지 기다려야 함을 의미합니다 echo b > fifo.

{ echo a > fifo; echo b > fifo; } & cat fifo

더 자세히 알아보려면 뒤에서 일어나는 기본 작업을 구분해야 합니다. echo a > fifo세 가지 작업을 결합합니다.

  1. 글쓰기 에 개방적입니다 fifo.
  2. a파일에 두 문자( 및 개행 문자)를 씁니다.
  3. 파일을 닫습니다.

이러한 작업이 다른 시간에 발생하도록 예약할 수 있습니다.

(
    exec >fifo     # 1. open
    sleep 1
    echo a         # 2. write
    sleep 1
)                  # 3. close

마찬가지로 cat foo열기, 읽기 및 닫기 작업이 결합됩니다. 다음과 같이 구분할 수 있습니다.

(
    exec <fifo     # 1. open
    sleep 1
    read line      # 2. read
    echo $line
    sleep 1
)                  # 3. close

( read쉘 내장은 실제로 여러 read시스템 호출을 할 수 있지만 지금은 중요하지 않습니다.)

Fifo는 실제로 파이프가 아닙니다. 이는 잠재적인 파이프라인과 더 유사합니다. fifo는 디렉토리 항목입니다. 프로세스가 읽기 위해 fifo를 열면 파이프 객체가 생성됩니다. 파이프가 없을 때 프로세스가 쓰기 위해 FIFO를 열면 open파이프가 생성될 때까지 호출이 차단됩니다. 또한 프로세스가 읽기 위해 fifo를 여는 경우 프로세스가 쓰기 위해 fifo를 열 때까지 이 작업도 차단됩니다(리더가 셸에 불편한 비차단 모드에서 파이프를 열지 않는 한). 따라서 명명된 파이프에 대한 첫 번째 열린 읽기와 첫 번째 열린 쓰기가 동시에 반환됩니다.

다음은 이 지식을 실제로 적용하는 쉘 스크립트입니다.

#!/bin/sh
tick () { sleep 0.1; echo tick; echo 0.1; }
mkfifo fifo
{
    exec <fifo; echo >&2 opened for reading;
    echo a; echo >&2 wrote a
} & writer=$!
tick
{
    exec >fifo; echo >&2 opened for writing;
    exec cat >&2;
} & reader=$!
wait $writer
kill $reader
rm fifo

두 개구부가 어떻게 동시에 발생하는지 확인하십시오(관찰할 수 있는 한 최대한 가깝게). 그리고 글쓰기는 그 이후에만 가능합니다.

참고: 위 스크립트에는 실제로 경쟁 조건이 있지만 파이프와는 아무 관련이 없습니다. 이러한 명령은 터미널에 기록되기 위해 echo >&2스크램블 중이므로 이전 및를 볼 수 있습니다. 시간을 더 정확하게 알고 싶다면 시스템 호출을 추적하면 됩니다. 예를 들어 Linux에서는 다음과 같습니다.cat >&2acatopening for writingwrote a

mkfifo fifo
strace -f -P fifo sh -c '…'

이제 두 명의 작가를 배치하면 두 작가 모두 리더가 도착할 때까지 시작 단계에서 차단됩니다. 누가 먼저 호출하는지는 중요하지 않습니다 open. 파이프는 공개 시도가 아닌 데이터에 대해 선입선출됩니다. WHO쓰다먼저 가장 중요합니다. 이를 실험하기 위한 스크립트는 다음과 같습니다.

#!/bin/sh
mkfifo fifo
{
    exec >fifo; echo >&2 opened for writing a
    sleep $1
    echo a; echo >&2 wrote a
} & writer_a=$!
{
    exec >fifo; echo >&2 opened for writing b
    sleep $2
    echo b; echo >&2 wrote b
} & writer_b=$!
sleep 0.2
cat fifo & reader=$!
wait $writer_a
wait $writer_b
kill $reader
rm fifo

스크립트는 판독기 a의 대기 시간과 작성자 b의 대기 시간이라는 두 가지 매개변수로 호출됩니다. 리더는 0.2초 안에 온라인 상태가 됩니다. 두 대기 시간이 모두 0.2초 미만이면 작성자가 온라인 상태가 되자마자 두 작성자 모두 쓰기를 시도하게 되며 이는 경쟁입니다. 반면, 대기 시간이 0.2보다 크면 먼저 온 사람이 먼저 출력을 얻습니다.

$ ./c 0.1 0.1
# Order of "opened for writing": random
# Order of "a"/"b": random
# Order of "wrote": random, might not match a/b due to echo racing against each other
$ ./c 0.3 0.4
# Order of "opened for writing": random
# Order of "wrote": a then b
# Order of "a"/"b": a then b

관련 정보