Resumidamente:
mkfifo fifo; (echo a > fifo) &; (echo b > fifo) &; cat fifo
O que eu esperava:
a
b
já que o primeiro echo … > fifo
deve ser o primeiro a abrir o arquivo, então espero que esse processo seja o primeiro a gravar nele (com o desbloqueio aberto primeiro).
O que eu ganho:
b
a
Para minha surpresa, esse comportamento também aconteceu ao abrir dois terminais separados para fazer a escrita em processos definitivamente independentes.
Estou entendendo mal alguma coisa sobre a semântica de primeiro a entrar, primeiro a sair de um pipe nomeado?
Stephen sugeriu adicionar um atraso:
#!/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 ))"
Agora, isso funciona 100% correto ( delay = 0.1, N = 100
).
Ainda assim, a execução mkfifo fifo; (echo a > fifo) &; sleep 0.1 ; (echo b > fifo) &; cat fifo
manual quase sempre produz a ordem invertida.
Na verdade, mesmo copiar e colar o for
próprio loop falha na metade das vezes. Estou muito confuso sobre o que está acontecendo aqui.
Isso não tem nada a ver com a semântica FIFO de pipes e não prova nada sobre eles de qualquer maneira. Tem a ver com o fato de que as FIFOs bloqueiam a abertura até que sejam abertas tanto para escrita quanto para leitura; então nada acontece até que seja
cat
abertofifo
para leitura.Iniciar processos em segundo plano significa que você não sabe quando eles serão realmente agendados, portanto, não há garantia de que o primeiro processo em segundo plano fará seu trabalho antes do segundo. O mesmo se aplica ao desbloqueio de processos bloqueados .
Você pode melhorar as chances, enquanto ainda usa processos em segundo plano, atrasando artificialmente o segundo:
Quanto maior o atraso, melhores as chances:
echo a > fifo
blocos esperando para terminar abrindofifo
,cat
inicia e abre ofifo
que desbloqueiaecho a
e depoisecho b
roda.Porém o principal fator aqui é quando
cat
abre o FIFO: até então, os shells bloqueiam tentando configurar os redirecionamentos. A ordem de saída vista depende, em última análise, da ordem em que os processos de escrita são desbloqueados.Você obterá resultados diferentes se executar
cat
primeiro:Dessa forma, a abertura
fifo
para escrita tenderá a não bloquear (ainda, sem garantias), então você veráa
primeiro com uma frequência maior do que na primeira configuração. Você também verácat
o acabamento antes dasecho b
execuções, ou seja , apenasa
a saída.Os tubos são primeiro a entrar, primeiro a sair. Seu problema é que você não entende quando o “in” acontece. O evento “in” está escrevendo , não abrindo.
Removendo a pontuação inútil, seu código é:
Isso executa os comandos
echo a > fifo
eecho b > fifo
em paralelo. O que quer que entre primeiro sairá primeiro, mas é uma corrida mais ou menos igual para saber quem entra primeiro.Se você quer
a
ser lido antesb
, você tem que providenciar para escrevê-lo antesb
. Isso significa que você tem que esperar até queecho a > fifo
seja feito antes de começarecho b > fifo
.Se você quiser ir mais longe, precisa distinguir entre as operações elementares que acontecem sob o capô.
echo a > fifo
combina três operações:fifo
para escrita.a
e uma nova linha) no arquivo.Você pode fazer com que essas operações aconteçam em momentos diferentes:
Da mesma forma,
cat foo
combina uma operação de abertura, leitura e fechamento. Você pode separá-los:(O
read
shell embutido pode realmente fazer váriasread
chamadas de sistema, mas isso não é importante agora.)Fifos na verdade não são bem tubos. Eles são mais como tubos em potencial. O fifo é uma entrada de diretório e um objeto pipe é criado quando um processo abre o fifo para leitura. Se um processo abre o fifo para escrita enquanto não existe pipe, a
open
chamada é bloqueada até que um pipe seja criado. Além disso, se um processo abre o fifo para leitura, esta operação também bloqueia até que um processo abra o fifo para escrita (a menos que o leitor abra o pipe no modo não bloqueante, o que não é conveniente do shell). Como consequência, o primeiro aberto para leitura e o primeiro aberto para escrita em um pipe nomeado retornarão ao mesmo tempo.Aqui está um script de shell que coloca esse conhecimento em ação.
Observe como ambas as aberturas acontecem ao mesmo tempo (o mais próximo que podemos observar). E a escrita só pode acontecer depois disso.
Nota: na verdade, há uma condição de corrida no script acima — mas não está relacionada aos tubos. Os
echo >&2
comandos estão correndo contra paracat >&2
gravar no terminal e, portanto, você pode ver antes e . Se você quiser ter uma visão mais precisa do tempo, você pode rastrear as chamadas do sistema. Por exemplo, no Linux:a
cat
opening for writing
wrote a
Agora se você colocar dois escritores, ambos os escritores vão bloquear na etapa de abertura até que o leitor chegue. Não importa quem inicia a
open
chamada primeiro: os pipes são os primeiros a entrar, primeiro a sair, não para tentativas abertas. Quem escreve primeiro é o que importa. Aqui está um script para experimentar isso.Chame o script com dois argumentos: o tempo de espera para o leitor a e o tempo de espera para o escritor b. O leitor fica online após 0,2 segundos. Se ambos os tempos de espera forem inferiores a 0,2 segundos, ambos os escritores tentarão escrever assim que os escritores ficarem online, e é uma corrida. Por outro lado, se os tempos de espera forem superiores a 0,2, quem chegar primeiro obtém a saída primeiro.