Aynı dosyayı aynı boru hattında okuma ve yazma her zaman “başarısız” olur?


9

Aşağıdaki komut dosyasına sahibim:

#!/bin/bash
for i in $(seq 1000)
do
    cp /etc/passwd tmp
    cat tmp | head -1 | head -1 | head -1 > tmp  #this is the key line
    cat tmp
done

Anahtar satırda, tmpbazen başarısız olan aynı dosyayı okudum ve yazdım .

(Yarış koşullarından dolayı okudum, çünkü boru hattındaki süreçler paralel olarak yürütülüyor, nedenini anlamıyorum - her headbirinin bir öncekinden veri alması gerekiyor, değil mi? Bu benim ana sorum DEĞİL, ama siz de cevaplayabilirsiniz.)

Komut dosyasını çalıştırdığımda, yaklaşık 200 satır çıktısı var. Ben bu komut dosyası her zaman 0 satır (böylece G / Ç yönlendirme tmpher zaman ilk hazırlanır ve böylece veri her zaman yok) çıktı zorlamak için herhangi bir yolu var mı ? Açıkça söylemek gerekirse, bu komut dosyasını değil sistem ayarlarını değiştirmek demek.

Fikirleriniz için teşekkürler.

Yanıtlar:


2

Gilles'in cevabı yarış durumunu açıklıyor. Sadece bu kısma cevap vereceğim:

Bu komut dosyasını her zaman 0 satır çıkmaya zorlayabilmemin bir yolu var mı (bu nedenle tmp'ye G / Ç yönlendirmesi her zaman önce hazırlanır ve böylece veriler her zaman yok edilir)? Açıkça söylemek gerekirse, sistem ayarlarını değiştirmek

Bunun için bir araç zaten varsa IDK, ama nasıl uygulanabileceği hakkında bir fikrim var. (Ancak bunun her zaman 0 satır olmayacağını , sadece bu gibi basit yarışları ve bazı daha karmaşık yarışları yakalayan kullanışlı bir testçi olduğunu unutmayın . Bkz. @Gilles'ın yorumu .) Bir komut dosyasının güvenli olduğunu garanti etmez , ancak ARM gibi zayıf sıralı x86 olmayan CPU'lar da dahil olmak üzere farklı CPU'larda çok iş parçacıklı bir programı test etmeye benzer, testte kullanışlı bir araç olabilir.

Şöyle çalıştırırdın racechecker bash foo.sh

İmkanları / izleme müdahale aynı sistem çağrısını kullanın strace -fve ltrace -fher çocuğun işleme ekleme kullanım. (Linux'ta, bu, ptraceGDB ve diğer hata ayıklayıcılar tarafından kesme noktalarını ayarlamak, tek adım ve başka bir işlemin belleğini / kayıtlarını değiştirmek için kullanılan sistem çağrısıdır .)

Cihaz openve openatsistem çağrıları: bu araç altında çalışan herhangi bir işlem bir open(2)sistem çağrısı (veya openat) ile yapıldığında O_RDONLY, belki 1/2 veya 1 saniye uyku. Diğer opensistem çağrılarının (özellikle dahil olanlar O_TRUNC) gecikmeden yürütülmesine izin verin.

Bu, yazarın sistem yükü de yüksek olmadığı sürece yarışı hemen hemen her yarış koşulunda kazanmasına izin vermeli veya kısalmanın başka bir okumadan sonraya kadar gerçekleşmediği karmaşık bir yarış koşuluydu. Bu nedenle , open()s (ve belki de read()s veya yazımların) geciktiği rastgele varyasyon, bu aracın algılama gücünü artıracaktır, ancak elbette, karşılaşabileceğiniz tüm olası durumları kapsayacak bir gecikme simülatörü ile sonsuz bir süre test etmeden gerçek dünyada, dikkatlice okuyup kanıtlamadıkça, senaryolarınızın yarışlardan arınmış olduğundan emin olamazsınız.


Muhtemelen openiçindeki dosyalar için beyaz listeye (gecikme değil ) ihtiyaç duyarsınız /usr/binve /usr/libbu nedenle işlem başlatma sonsuza kadar sürmez. (Çalışma zamanı dinamik bağlantı open()birden fazla dosyaya sahiptir (bakın strace -eopen /bin/trueveya bir /bin/lsara), ancak üst kabuk kesimi yapıyorsa, bu tamam olacaktır. Ancak bu aracın komut dosyalarını mantıksız derecede yavaş yapmaması iyi olacaktır).

Ya da çağıran sürecin ilk etapta kısaltma izni olmayan her dosyayı beyaz listeye ekleyin. yani izleme işlemi, bir dosyayı access(2)isteyen işlemi gerçekten askıya almadan önce bir sistem çağrısı yapabilir open().


racecheckerKendisinin C harfiyle yazılmış olması gerekir, ancak kabukta değil, belki de stracekodunu başlangıç ​​noktası olarak kullanabilir ve uygulamak için fazla çalışma gerektirmeyebilir.

Belki de aynı işlevi bir FUSE dosya sistemi ile alabilirsiniz . Muhtemelen saf geçişli bir dosya sistemi için bir FUSE örneği vardır, bu nedenle open()işlevi salt okunur açılar için uyku haline getiren ancak kesmenin hemen gerçekleşmesine izin veren işleve kontroller ekleyebilirsiniz .


Bir yarış denetleyicisi fikriniz gerçekten işe yaramıyor. İlk olarak, zaman aşımlarının güvenilir olmadığı sorunu var: bir gün diğer adam beklediğinizden daha uzun sürecek (bu, bir süre çalışıyor gibi görünen ve daha sonra hata ayıklaması zor olan başarısız olan derleme veya test komutlarıyla klasik bir sorundur. iş yükü genişlediğinde ve birçok şey paralel çalıştığında). Ama bunun ötesinde, hangi açıklığa gecikme ekleyeceksiniz? İlginç bir şey tespit etmek için, farklı gecikme kalıpları ile birçok çalışma yapmanız ve sonuçlarını karşılaştırmanız gerekir.
Gilles 'SO- kötü olmayı bırak

@Gilles: Doğru, makul derecede kısa bir gecikme, kesmenin yarışı kazanacağını garanti etmez (işaret ettiğinizde ağır yüklü bir makinede). Buradaki fikir , bunu her zaman kullandığınızdan değil, komut dosyanızı birkaç kez test etmek için kullanmanızdır racechecker. Ve muhtemelen, 10 saniye gibi daha yüksek ayarlamak isteyen çok yüklü makinelerde bulunan kişilerin yararına yapılandırılabilir olmak için okumak için açık uyku süresini açmak istersiniz. Veya dosyaları çok fazla yeniden açan uzun veya verimsiz komut dosyaları için 0,1 saniye gibi daha düşük bir değere ayarlayın .
Peter Cordes

@Gilles: OP'nin durumu gibi, "aynı olması gereken (kabukların nasıl çalıştığını bildiğinizde)" gibi basit boru hatlarından daha fazla yarış yakalamanıza izin veren farklı gecikme kalıpları hakkında harika bir fikir. Ama "hangisi açılıyor?" herhangi bir salt okunur açık, bir beyaz liste veya işlem başlangıcını geciktirmemek için başka bir yolla.
Peter Cordes

Sanırım başka bir süreç tamamlanıncaya kadar kısalmayan arka plan işleriyle daha karmaşık ırklar mı düşünüyorsunuz? Evet, bunu yakalamak için rastgele varyasyon gerekebilir. Veya belki de işlem ağacına bakın ve gecikme "erken" daha fazla okur, normal sıralamayı tersine çevirmek için. Daha fazla yeniden sıralama olasılığını simüle etmek için aracı gittikçe daha karmaşık hale getirebilirsiniz, ancak bir noktada çoklu görev yapıyorsanız programlarınızı doğru bir şekilde tasarlamanız gerekir. Otomatik test, olası sorunların daha sınırlı olduğu daha basit komut dosyaları için yararlı olabilir.
Peter Cordes

Çok iş parçacıklı kodun, özellikle kilitsiz algoritmaların test edilmesine oldukça benzer: testin yanı sıra neden doğru olduğuna dair mantıksal akıl yürütme ve test etme, çünkü olabilecek tüm yeniden siparişleri üretmek için herhangi bir belirli makine setinde test yapmaya güvenemezsiniz. tüm boşlukları kapatmadıysanız sorun olabilir. Ancak, ARM veya PowerPC gibi zayıf sıralı bir mimaride test etmek, uygulamada iyi bir fikirdir, bir sistemi altında şeyleri yapay olarak geciktiren bazı yarışları ortaya çıkarabilir , bu yüzden hiçbir şeyden daha iyi değildir. Her zaman yakalamayacak böcekler tanıtabilirsiniz!
Peter Cordes

18

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 -xher 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):

  1. Harici programı catargümanla yürütün tmp.
  2. Açık tmpokuma.
  3. 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:

  1. İşlemdeki tmpdosyayı kısaltarak standart çıktıyı yeniden yönlendirin .
  2. Harici programı headargümanla yürütün -1.
  3. 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 tmpve okumasını söylediniz tmp, bu yüzden iki şeyi paralel olarak yapar.

Tamam, değiştirebileceğiniz bir “sistem ayarı” var: yerine /bin/bashbash 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) catokumayı 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 catkesme 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.


Bu benim amacımdı, senaryonun iyi yazılmış olup olmadığını belirlemek. Senaryo bu şekilde verileri imha etmiş olsaydı, her seferinde onları yok etmesini istedim. Bunun neredeyse imkansız olduğunu duymak iyi değil. Sayende artık sorunun ne olduğunu biliyorum ve bir çözüm düşünmeye çalışacağım.
karlosss

@karlosss: Hmm, tüm okuma için sistem çağrılarını (tüm alt süreçlerde) yarım saniyeliğine uyutmak için strace(örneğin Linux ptrace) ile aynı sistem çağrısı izleme / yakalama öğelerini kullanıp kullanamayacağınızı merak ediyorum. openbir kesme, neredeyse kesme her zaman kazanacaktır.
Peter Cordes

@PeterCordes Ben bu konuda bir acemi, eğer bunu başarmak ve cevap olarak yazmak için bir yol yönetebilirseniz, ben kabul edeceğim.
karlosss

@PeterCordes Kesmenin bir gecikmeyle kazanacağını garanti edemezsiniz. Çoğu zaman işe yarayacaktır, ancak bazen aşırı yüklü bir makinede komut dosyanız az çok gizemli yollarla başarısız olacaktır.
Gilles 'SO- kötü olmayı bırak

@Gilles: Bunu cevabım altında tartışalım.
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.