Neden bir yarış durumu var?
Bir borunun iki tarafı birbiri ardına değil paralel olarak yürütülür. Bunu göstermenin çok basit bir yolu var: koş
time sleep 1 | sleep 1
Bu bir saniye sürer, iki değil.
Kabuk iki alt süreç başlatır ve her ikisinin de tamamlanmasını bekler. Bu iki süreç paralel olarak yürütülür: bunlardan birinin diğeri ile senkronize olmasının tek nedeni diğerini beklemesi gerektiğidir . Senkronizasyonun en yaygın noktası, sağ tarafın standart girdisinde okunmasını bekleyen blokları bloke etmesi ve sol taraf daha fazla veri yazması durumunda engeli kaldırılmasıdır. Ayrıca, sağ taraf verileri okumak için yavaş olduğunda ve sağ taraf daha fazla veri okuyana kadar yazma işleminde sol taraf blokları olduğunda (borunun kendisinde, Çekirdek, ancak küçük bir maksimum boyuta sahiptir).
Bir senkronizasyon noktasını gözlemlemek için aşağıdaki komutları uygulayın ( sh -x
her komutu yürütürken yazdırır):
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
Gözlemlediğiniz şeyden memnun olana kadar varyasyonlarla oynayın.
Bileşik komut verildiğinde
cat tmp | head -1 > tmp
sol taraftaki işlem aşağıdakileri yapar (yalnızca açıklamamla ilgili adımları listeledim):
- Harici programı
cat
argümanla yürütün tmp
.
- Açık
tmp
okuma.
- Dosyanın sonuna ulaşmamış olsa da, dosyadan bir yığın okuyun ve standart çıktıya yazın.
Sağ işlem aşağıdakileri yapar:
- İşlemdeki
tmp
dosyayı kısaltarak standart çıktıyı yeniden yönlendirin .
- Harici programı
head
argümanla yürütün -1
.
- Standart girişten bir satır okuyun ve standart çıkışa yazın.
Senkronizasyonun tek noktası, sağ-3'ün, sol-3'ün bir tam çizgi işlemesini beklemesidir. Sol-2 ve sağ-1 arasında senkronizasyon yoktur, bu nedenle her iki sırada da olabilirler. Hangi sırayla gerçekleştikleri tahmin edilemez: CPU mimarisine, kabuğa, çekirdeklerin, planlandığı çekirdeklerin, CPU'nun bu süre zarfında ne gibi kesintilere uğradığına vb.
Davranış nasıl değiştirilir
Bir sistem ayarını değiştirerek davranışı değiştiremezsiniz. Bilgisayar size söylediklerinizi yapar. Paralel olarak kesilmesini tmp
ve okumasını söylediniz tmp
, bu yüzden iki şeyi paralel olarak yapar.
Tamam, değiştirebileceğiniz bir “sistem ayarı” var: yerine /bin/bash
bash olmayan farklı bir program kullanabilirsiniz. Umarım bu iyi bir fikir değildir.
Kesmenin borunun sol tarafından önce olmasını istiyorsanız, boru hattının dışına koymanız gerekir, örneğin:
{ cat tmp | head -1; } >tmp
veya
( exec >tmp; cat tmp | head -1 )
Bunu neden isteyeceğine dair hiçbir fikrim yok. Boş olduğunu bildiğiniz bir dosyadan okumanın anlamı nedir?
Tersine, çıkış yeniden yönlendirmesinin (kesme dahil) cat
okumayı bitirdikten sonra olmasını istiyorsanız, bellekteki verileri tam olarak arabelleğe almanız gerekir, örn.
line=$(cat tmp | head -1)
printf %s "$line" >tmp
veya farklı bir dosyaya yazıp yerine taşıyın. Bu genellikle komut dosyalarında bir şeyler yapmanın sağlam yoludur ve dosyanın orijinal adla görünür olmadan önce tam olarak yazılması avantajına sahiptir.
cat tmp | head -1 >new && mv new tmp
Moreutils koleksiyonu şimdi aradı, bunu yapar bir program içermektedir sponge
.
cat tmp | head -1 | sponge tmp
Sorun otomatik olarak nasıl algılanır
Amacınız kötü yazılmış komut dosyaları almak ve otomatik olarak nerede kırıldıklarını bulmaksa, üzgünüm, hayat o kadar basit değil. Çalışma zamanı analizi sorunu güvenilir bir şekilde bulamaz, çünkü bazen cat
kesme işlemi yapılmadan okumayı bitirir. Statik analiz prensipte yapabilir; sorunuzdaki basitleştirilmiş örnek Shellcheck tarafından yakalanıyor , ancak daha karmaşık bir komut dosyasında benzer bir sorun yakalamayabilir.