간단히 말해서:
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
실행합니다.
그러나 여기서 주요 요인은 cat
FIFO가 켜져 있을 때입니다. 그때까지 쉘 블록은 리디렉션을 설정하려고 시도합니다. 표시되는 출력 순서는 궁극적으로 쓰기 프로세스가 잠금 해제되는 순서에 따라 달라집니다.
먼저 실행하면 다른 결과가 나타납니다 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
세 가지 작업을 결합합니다.
- 글쓰기 에 개방적입니다
fifo
. a
파일에 두 문자( 및 개행 문자)를 씁니다.- 파일을 닫습니다.
이러한 작업이 다른 시간에 발생하도록 예약할 수 있습니다.
(
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 >&2
a
cat
opening for writing
wrote 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