Uçucu, çok iş parçacıklı C veya C ++ programlamasında neden yararlı değildir?


165

Son zamanlarda gönderdiğim bu cevapta gösterildiği gibi volatile, çok iş parçacıklı programlama bağlamlarının faydası (veya eksikliği) hakkında kafam karışmış gibi görünüyor .

Benim anlayışım şudur: Bir değişken, ona erişen bir kod parçasının kontrol akışının dışında her değiştirilebildiğinde, bu değişken olduğu bildirilmelidir volatile. Sinyal işleyicileri, G / Ç kayıtları ve başka bir iş parçacığı tarafından değiştirilen değişkenlerin tümü bu tür durumları oluşturur.

Bu nedenle, global bir int'iniz varsa foove foobir iş parçacığı tarafından okunuyorsa ve başka bir iş parçacığı tarafından atomik olarak ayarlanmışsa (muhtemelen uygun bir makine talimatı kullanarak), okuma iş parçacığı bu durumu bir sinyal işleyici tarafından ayarlanmış bir değişkeni görür gibi görür veya harici bir donanım koşulu tarafından değiştirilir ve bu nedenle foobildirilmelidir volatile(veya çok iş parçacıklı durumlar için, bellekle çevrili yük ile erişilir, bu muhtemelen daha iyi bir çözümdür).

Nasıl ve nerede yanılıyorum?


7
Tüm değişken, derleyicinin geçici bir değişkene erişimi önbelleğe almaması gerektiği anlamına gelir. Böyle bir erişimin serileştirilmesi hakkında hiçbir şey söylemiyor. Burada tartışıldı kaç kez bilmiyorum ve bu sorunun bu tartışmalara bir şey katacağını sanmıyorum.

4
Ve yine, bunu hak etmeyen ve burada daha önce birçok kez sorulan bir soru kaldırıldı. Lütfen bunu yapmayı keser misin.

14
@neil Diğer soruları araştırdım ve bir tane buldum, ancak bir şekilde gördüğüm herhangi bir açıklama, neden yanlış olduğumu gerçekten anlamak için ihtiyacım olan şeyi tetiklemedi. Bu soru böyle bir cevap ortaya çıkardı.
Michael Ekstrand

1
CPU'ların verilerle (önbellekleri aracılığıyla) neler yaptığını ayrıntılı bir şekilde incelemek için şunları
Sassafras_wot

1
@curiousguy "C'de durum böyle değil" ile kastediyorum, donanım kayıtlarına vb. yazmak için kullanılabilir ve Java'da yaygın olarak kullanıldığı gibi çoklu kullanım için kullanılmaz.
Monstieur

Yanıtlar:


213

Çok volatileiş parçacıklı bağlamdaki sorun, ihtiyacımız olan tüm garantileri sağlamamasıdır . İhtiyacımız olan birkaç özelliğe sahiptir, ancak hepsine değil, bu yüzden volatile yalnız kalamayız .

Bununla birlikte, geri kalan mülkler için kullanmamız gereken ilkeller de bunu sağlayanları sağlar volatile, bu nedenle etkili bir şekilde gereksizdir.

Paylaşılan verilere iş parçacığı güvenli erişim için aşağıdakileri garanti etmemiz gerekir:

  • okuma / yazma aslında gerçekleşir (derleyici sadece değeri bir kayıt defterinde saklamaz ve daha sonraya kadar ana belleği güncellemeyi ertelemez)
  • yeniden sıralama yapılmadığını. volatileBazı değişkenlerin okunmaya hazır olup olmadığını belirtmek için bir değişkeni bayrak olarak kullandığımızı varsayın . Kodumuzda, verileri hazırladıktan sonra bayrağı ayarladık, böylece her şey iyi görünüyor . Peki ya bayrak önce ayarlanacak şekilde talimatlar yeniden sıralanırsa ?

volatileilk noktayı garanti eder. Ayrıca , farklı geçici okuma / yazma işlemleri arasında yeniden sıralama yapılmamasını da garanti eder . Tüm volatilebellek erişimleri belirtildikleri sırayla gerçekleşir. volatileAmaçlanan şey için ihtiyacımız olan tek şey budur: G / Ç kayıtlarını veya bellek eşlemeli donanımı değiştirmek, ancak volatilenesnenin genellikle kalıcı olmayan verilere erişimi senkronize etmek için kullanıldığı çok iş parçacıklı kodda bize yardımcı olmaz . Bu erişimler yine de erişimlere göre yeniden sıralanabilir volatile.

Yeniden sıralamayı önlemenin çözümü , hem derleyiciye hem de CPU'ya bu noktada bellek erişiminin yeniden sıralanamayacağını gösteren bir bellek bariyeri kullanmaktır . Bu tür engelleri değişken değişken erişimimizin etrafına yerleştirmek, geçici olmayan erişimlerin bile geçici erişim boyunca yeniden sıralanmamasını sağlayarak, iş parçacığı için güvenli kod yazmamızı sağlar.

Ancak, bellek engelleri de bu yüzden etkili bir şekilde bize biz yapma kendisi tarafından gereken her şeyi verir, beklemedeki tüm yazıyor bariyer ulaşıldığında yürütür / okur sağlamak volatilegereksiz. volatileNiteleyiciyi tamamen kaldırabiliriz .

C ++ 11'den beri, atomik değişkenler ( std::atomic<T>) bize tüm ilgili garantileri verir.


5
@jbcreix: Hangisini soruyorsun? Geçici veya bellek engelleri? Her durumda, cevap hemen hemen aynı. Her ikisi de derleyici ve CPU düzeyinde çalışmalıdır, çünkü programın gözlemlenebilir davranışını tanımlarlar - bu nedenle CPU'nun her şeyi yeniden düzenlemediğinden ve garanti ettikleri davranışı değiştirmediğinden emin olmak zorundadırlar. Ancak şu anda taşınabilir iş parçacığı eşitlemesi yazamazsınız, çünkü bellek engelleri standart C ++ 'ın bir parçası değildir (bu nedenle taşınabilir değildir) ve volatilekullanışlı olacak kadar güçlü değildir.
jalf

4
Bir MSDN örneği bunu yapar ve talimatların geçici bir erişimden sonra yeniden sıralanamayacağını iddia eder: msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx
OJW

27
@OJW: Ancak Microsoft'un derleyicisi volatiletam bir bellek engeli olarak yeniden tanımlıyor (yeniden sıralamayı engelliyor). Bu standardın bir parçası değildir, bu nedenle taşınabilir kodda bu davranışa güvenemezsiniz.
jalf

4
@Skizz: hayır, denklemin "derleyici büyüsü" kısmının devreye girdiği yer burası. Hem CPU hem de derleyici tarafından bir bellek bariyeri anlaşılmalıdır . Derleyici bir bellek bariyerinin anlambilimini anlarsa, bunun gibi hilelerden kaçınmayı bilir (ayrıca bariyer boyunca okuma / yazma yeniden sıralama). Ve neyse ki, derleyici yapar her şey yoluna girer, böylece en sonunda, bir bellek bariyer anlamını tam. :)
jalf

13
@Skizz: İş parçacıklarının kendileri her zaman C ++ 11 ve C11'den önce platforma bağımlı bir uzantıdır. Bildiğim kadarıyla, bir iş parçacığı uzantısı sağlayan her C ve C ++ ortamı da bir "bellek bariyeri" uzantısı sağlar. Ne olursa olsun, volatileçok iş parçacıklı programlama için her zaman işe yaramaz. (Uçucu Visual Studio, haricinde olan bellek bariyer uzantısı.)
Nemo

49

Bunu Linux Çekirdek Belgeleri'nden de düşünebilirsiniz .

C programcıları genellikle değişkenin mevcut yürütme iş parçasının dışında değiştirilebileceği anlamına gelen uçucu hale gelmiştir; sonuç olarak, paylaşılan veri yapıları kullanıldığında bazen çekirdek kodunda kullanmaya eğilimlidirler. Başka bir deyişle, uçucu türleri, kolay olmayan bir tür atomik değişken olarak tedavi ettikleri bilinmektedir. Çekirdek kodunda uçucu kullanımı neredeyse hiçbir zaman doğru değildir; bu belge nedenini açıklamaktadır.

Değişken ile ilgili anlaşılması gereken kilit nokta, amacının optimizasyonu bastırmaktır, ki bu aslında hiç yapmak istemediği şeydir. Çekirdekte, paylaşılan veri yapılarını, çok farklı bir görev olan istenmeyen eşzamanlı erişime karşı korumak gerekir. İstenmeyen eşzamanlılığa karşı koruma süreci, optimizasyonla ilgili neredeyse tüm sorunları daha verimli bir şekilde önleyecektir.

Uçucu gibi, verilere aynı anda erişimi sağlayan çekirdek ilkelleri (kilitlenmeler, muteksler, bellek bariyerleri, vb.) İstenmeyen optimizasyonu önlemek için tasarlanmıştır. Düzgün bir şekilde kullanılıyorlarsa, uçucu da kullanmaya gerek kalmayacaktır. Uçucu hala gerekliyse, kodda bir yerde neredeyse kesinlikle bir hata vardır. Düzgün yazılmış çekirdek kodunda, uçucu yalnızca işleri yavaşlatmaya yarar.

Tipik bir çekirdek kodu bloğunu düşünün:

spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);

Tüm kod kilitleme kurallarına uyarsa, the_lock basılıyken shared_data değeri beklenmedik şekilde değişemez. Bu verilerle oynamak isteyebilecek diğer tüm kodlar kilitte bekliyor olacaktır. Spinlock ilkelleri bellek bariyerleri gibi davranırlar - açıkça yapmak için yazılırlar - yani veri erişimleri bunlar arasında optimize edilmez. Böylece derleyici, paylaşılan_verilerde ne olacağını bildiğini düşünebilir, ancak spin_lock () çağrısı, bir bellek engeli görevi gördüğünden, bildiği her şeyi unutmaya zorlar. Bu verilere erişimde hiçbir optimizasyon problemi olmayacaktır.

Shared_data değişken olarak bildirilirse, kilitleme yine de gerekli olacaktır. Ancak, başka kimsenin onunla çalışamayacağını bildiğimizde , derleyicinin kritik bölüm içindeki paylaşılan_verilere erişimi optimize etmesi de engellenir . Kilit basılı tutulurken shared_data değişken olmaz. Paylaşılan verilerle uğraşırken uygun kilitleme, uçucu gereksiz ve potansiyel olarak zararlı hale getirir.

Uçucu depolama sınıfı başlangıçta bellek eşlemeli G / Ç kayıtları içindir. Çekirdek içinde, yazmaç erişimi de kilitlerle korunmalıdır, ancak derleyici kritik bir bölüm içinde kayıt erişimini "optimize etmek" istemez. Ancak, çekirdek içinde G / Ç bellek erişimleri her zaman erişimci işlevleri aracılığıyla yapılır; G / Ç belleğine doğrudan işaretçilerden erişerek kaşlarını çatar ve tüm mimarilerde çalışmaz. Bu erişimciler istenmeyen optimizasyonu önlemek için yazılmıştır, bu nedenle bir kez daha uçucu olmak gereksizdir.

Birinin uçucu kullanmak için cazip gelebileceği bir başka durum, işlemcinin bir değişkenin değerini beklerken meşgul olmasıdır. Yoğun bir bekleme gerçekleştirmenin doğru yolu:

while (my_variable != what_i_want)
    cpu_relax();

Cpu_relax () çağrısı, CPU güç tüketimini azaltabilir veya hiper iş parçacıklı bir çift işlemciye verim verebilir; aynı zamanda bir bellek engeli görevi görür, bu yüzden bir kez daha uçucu olmak gereksizdir. Tabii ki, meşgul beklemek genellikle başlamak için anti-sosyal bir eylemdir.

Çekirdeğin içinde uçucunun mantıklı olduğu birkaç nadir durum vardır:

  • Yukarıda belirtilen erişimci işlevleri, doğrudan G / Ç bellek erişiminin çalıştığı mimarilerde uçucu olabilir. Esasen, her erişimci çağrısı kendi başına küçük bir kritik bölüm haline gelir ve erişimin programcı tarafından beklendiği gibi gerçekleşmesini sağlar.

  • Belleği değiştiren, ancak başka görünür yan etkisi olmayan satıriçi montaj kodu, GCC tarafından silinme riskidir. Asm ifadelerine geçici anahtar kelime eklemek bu kaldırmayı önleyecektir.

  • Jiffies değişkeni, her başvuru yapıldığında farklı bir değere sahip olabileceği için özeldir, ancak herhangi bir özel kilitleme olmadan okunabilir. Yani jiffies uçucu olabilir, ancak bu tipteki diğer değişkenlerin eklenmesi kesinlikle kaşlarını çatmıştır. Jiffies bu konuda bir "aptal miras" sorunu (Linus'un sözleri) olarak kabul edilir; sabitlemek değerinden daha fazla sorun olurdu.

  • G / Ç aygıtları tarafından değiştirilebilen tutarlı bellekteki veri yapılarına işaretçiler, bazen yasal olarak değişken olabilir. Ağ bağdaştırıcısı tarafından kullanılan ve bağdaştırıcının hangi tanımlayıcıların işlendiğini belirtmek için işaretçileri değiştirdiği bir halka arabelleği, bu tür bir duruma örnektir.

Çoğu kod için, uçucuya ilişkin yukarıdaki gerekçelerin hiçbiri geçerli değildir. Sonuç olarak, uçucu madde kullanımının bir hata olarak görülmesi muhtemeldir ve koda ilave inceleme getirecektir. Uçucu kullanmaya meyilli olan geliştiriciler geri adım atmalı ve gerçekten neyi başarmaya çalıştıklarını düşünmelidir.



1
Spin_lock () normal bir işlev çağrısına benziyor. Oluşturulan kodun spin_lock () 'den önce okunan ve bir kayıtta saklanan paylaşılan_verilerin herhangi bir değerini "unutacağı" ve böylece değerin spin_lock () sonrasında do_something_on ()?
SYNCOPATED

1
@underscore_d Demek istediğim spin_lock () işlev adından özel bir şey yaptığını söyleyemem. İçinde ne olduğunu bilmiyorum. Özellikle, derleyicinin sonraki okumaları optimize etmesini engelleyen uygulamada ne olduğunu bilmiyorum.
SYNCOPATED

1
Senkoplu iyi bir noktaya sahiptir. Bu aslında programcının bu "özel fonksiyonların" dahili uygulamasını bilmesi veya en azından davranışları hakkında çok iyi bilgilendirilmesi gerektiği anlamına gelir. Bu, aşağıdaki gibi ek soruları ortaya çıkarır - bu özel işlevler tüm mimarilerde ve tüm derleyicilerde aynı şekilde çalışacak şekilde standartlaştırılmış ve garanti edilmiş mi? Bu tür fonksiyonların bir listesi var mı, en azından söz konusu fonksiyonun kodu "optimize edilmeye" karşı koruduğunu gösteren geliştiricilere işaret etmek için kod yorumlarını kullanmak için bir kural var mı?
JustAMartin

1
@Tuntable: Özel bir statik, bir işaretçi aracılığıyla herhangi bir kodla dokunabilir. Ve adresi alınıyor. Belki de veri akışı analizi, işaretçinin asla kaçmadığını kanıtlayabilir, ancak bu genellikle program boyutunda süper doğrusal olan çok zor bir sorundur. Takma ad bulunmadığını garanti etmenin bir yolu varsa, erişimi bir döndürme kilidine taşımak aslında iyi olmalıdır. Ancak takma ad yoksa, volatileanlamsızdır. Her durumda, "bedeni görülemeyen bir işleve çağrı" davranışı doğru olacaktır.
Ben Voigt

11

Yanlış olduğunu düşünmüyorum - değer A iş parçacığı dışında bir şey tarafından değiştirilirse, A iş parçacığının değer değişikliğini göreceğini garanti etmek için uçucu olması gerekir. Anladığım kadarıyla, uçucu temelde anlatmanın bir yoludur derleyici "bu değişkeni bir kayıt defterinde önbelleğe almayın, bunun yerine her erişimde her zaman RAM bellekten okuduğunuzdan / yazdığınızdan emin olun".

Kafa karışıklığı, bir çok şeyi uygulamak için uçucunun yeterli olmamasıdır. Özellikle, modern sistemler birden çok önbellekleme seviyesi kullanır, modern çok çekirdekli CPU'lar çalışma zamanında bazı fantezi optimizasyonları yapar ve modern derleyiciler derleme zamanında bazı fantezi optimizasyonları yapar ve bunların hepsi farklı yan etkilerin ortaya çıkmasıyla sonuçlanabilir. sadece kaynak koduna baksaydınız beklediğiniz sıradan sipariş verin.

Bu nedenle, uçucu değişkendeki 'gözlenen' değişikliklerin tam olarak düşündükleri zamanda gerçekleşmeyebileceğini aklınızda bulundurduğunuz sürece, uçucu iyidir. Özellikle, uçucu değişkenleri, iş parçacıkları arasındaki işlemleri eşitlemenin veya sipariş etmenin bir yolu olarak kullanmaya çalışmayın, çünkü güvenilir bir şekilde çalışmaz.

Şahsen, uçucu bayrak için benim ana (sadece?) Kullanımı bir "pleaseGoAwayNow" boole gibidir. Sürekli döngü yapan bir işçi iş parçacığım varsa, döngüdeki her yinelemede uçucu boolean'ı kontrol etmesini ve boolean doğru olduğunda çıkış yapmam gerekir. Daha sonra ana iş parçacığı, boolean değerini true olarak ayarlayıp işçi iş parçacığı gidene kadar beklemek için pthread_join () öğesini çağırarak işçi iş parçacığını güvenle temizleyebilir.


2
Boole bayrağınız muhtemelen güvensizdir. İşçinin görevini tamamladığını ve bayrağın okunana kadar kapsamda kalmasını nasıl garanti edersiniz? Bu sinyaller için bir iş. Uçucu basit spinlocks uygulamak için iyidir eğer takma emniyet derleyici varsayar demektir, çünkü hiçbir muteks ilgilenmektedir mutex_lock(ve diğer her kütüphane fonksiyonu) bayrak değişkenin durumunu değiştirebilir.
Mart'ta Potatoswatter

6
Açıkçası, yalnızca işçi ipliğinin rutininin doğası, booleanın periyodik olarak kontrol edileceği garanti edildiyse işe yarar. İş parçacığı-kapatma sırası her zaman uçucu-boolean'ı tutan nesne yok edilmeden önce gerçekleşir ve iş parçacığı kapatma sırası, bool ayarlandıktan sonra pthread_join () öğesini çağırır. pthread_join (), çalışan iş parçacığı gidene kadar engeller. Sinyallerin, özellikle çoklu kullanım ile birlikte kullanıldığında kendi sorunları vardır.
Jeremy Friesner

2
İşçi iş parçacığının , boolean doğru olmadan işini tamamlaması garanti edilmez - aslında, bool true değerine ayarlandığında neredeyse bir iş biriminin ortasında olacaktır. Ancak, iş parçacığı iş birimini tamamladığında önemli değildir, çünkü ana iş parçacığı, her durumda, iş parçacığı çıkana kadar pthread_join () içinde engelleme dışında bir şey yapmayacaktır. Bu nedenle kapatma sırası iyi sıralanmıştır - uçucu bool (ve diğer paylaşılan veriler) pthread_join () geri dönene kadar serbest bırakılmaz ve pthread_join () çalışan iş parçacığı gidene kadar geri dönmez.
Jeremy Friesner

10
@Jeremy, pratikte haklısın ama teorik olarak hala kırılabilir. İki çekirdekli bir sistemde bir çekirdek sürekli olarak iş parçacığınızı yürütüyor. Diğer çekirdek boolü doğru yapar. Ancak, iş parçacığının çekirdeğinin bu değişikliği göreceğinin garantisi yoktur, yani boolü tekrar tekrar kontrol etmesine rağmen asla duramayabilir. Bu davranış, c ++ 0x, java ve c # bellek modelleri tarafından izin verilir. Pratikte, bu hiçbir zaman meşgul iplik büyük olasılıkla bir yere bir bellek bariyeri eklediğinden, bundan sonra booldeki değişikliği görecektir.
deft_code

4
Bir POSIX sistemi alın, gerçek zamanlı zamanlama politikası kullanın, sistemdeki SCHED_FIFOdiğer süreçlerden / iş parçacıklarından daha yüksek statik öncelik kullanın , yeterli çekirdek, mükemmel bir şekilde mümkün olmalıdır. Linux'ta, gerçek zamanlı işlemin CPU zamanının% 100'ünü kullanabileceğini belirtebilirsiniz. Daha yüksek öncelikli iş parçacığı / işlem yoksa hiçbir zaman bağlam anahtarı olmaz ve hiçbir zaman G / Ç tarafından engellenmez. Ancak önemli olan nokta, C / C ++ ' volatilenin uygun veri paylaşımı / senkronizasyon semantiğinin uygulanması için tasarlanmamış olmasıdır. Yanlış kodların bazen işe yarayabileceğini kanıtlamak için özel durumlar aramanın işe yaramaz bir egzersiz olduğunu düşünüyorum.
FooF

7

volatilebir spinlock muteksinin temel yapısını uygulamak için (yetersiz de olsa) yararlıdır, ancak bir kez (veya daha üstün bir şey) olduğunda, başka bir şeye ihtiyacınız yoktur volatile.

Çok iş parçacıklı programlamanın tipik yolu, paylaşılan her değişkeni makine düzeyinde korumak değil, program akışını yönlendiren koruma değişkenleri tanıtmaktır. Onun yerine sahip volatile bool my_shared_flag;olmalısın

pthread_mutex_t flag_guard_mutex; // contains something volatile
bool my_shared_flag;

Bu sadece "sert kısmı" kapsamakla kalmaz, aynı zamanda temelde gereklidir: C muteksi uygulamak için gerekli atomik işlemleri içermez ; sadece olağan operasyonlar volatileiçin ekstra garanti vermek zorundadır .

Şimdi böyle bir şey var:

pthread_mutex_lock( &flag_guard_mutex );
my_local_state = my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

pthread_mutex_lock( &flag_guard_mutex ); // may alter my_shared_flag
my_shared_flag = ! my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

my_shared_flag önbelleğe alınamaz olmasına rağmen uçucu olması gerekmez, çünkü

  1. Başka bir iş parçacığının buna erişimi var.
  2. Yani bir zamanlar ( &operatör ile) bir referans alınmış olmalıdır .
    • (Ya da içeren bir yapıya bir referans alınmıştır)
  3. pthread_mutex_lock bir kütüphane fonksiyonudur.
  4. Yani derleyici bir pthread_mutex_lockşekilde bu referansı alıp almadığını söyleyemez .
  5. Derleyici Anlamı gerekir varsayalım o pthread_mutex_lockmodifes paylaşılan bayrağı !
  6. Dolayısıyla değişkenin bellekten yeniden yüklenmesi gerekir. volatile, bu bağlamda anlamlı olmakla birlikte, yabancıdır.

6

Anlayışınız gerçekten yanlış.

Uçucu değişkenlerin sahip olduğu özellik "bu değişkenden okunur ve yazılır", programın algılanabilir davranışının bir parçasıdır ". Bu, programın çalıştığı anlamına gelir (uygun donanım verildiğinde):

int volatile* reg=IO_MAPPED_REGISTER_ADDRESS;
*reg=1; // turn the fuel on
*reg=2; // ignition
*reg=3; // release
int x=*reg; // fire missiles

Sorun şu ki, bu iş parçacığı için güvenli bir şey istediğimiz özellik değildir.

Örneğin, iş parçacığı için güvenli bir sayaç sadece (linux-çekirdek benzeri kod, c ++ 0x eşdeğerini bilmiyorum) olacaktır:

atomic_t counter;

...
atomic_inc(&counter);

Bu atomik, bellek engeli yok. Gerekirse bunları eklemelisiniz. Uçucu eklemek muhtemelen yardımcı olmaz, çünkü yakındaki koda erişimi (örneğin sayacın saydığı listeye bir eleman eklemekle) ilişkilendirmez. Kesinlikle, sayacın programınızın dışında arttığını görmenize gerek yoktur ve optimizasyonlar hala arzu edilir, örn.

atomic_inc(&counter);
atomic_inc(&counter);

hala optimize edilebilir

atomically {
  counter+=2;
}

optimize edici yeterince akıllıysa (kodun anlambilimini değiştirmez).


6

Verilerinizin eşzamanlı bir ortamda tutarlı olması için iki koşul uygulamanız gerekir:

1) Atomisite, yani belleğe bir miktar veri okuduğumda veya yazdığımda, veriler bir geçişte okunur / yazılır ve örneğin bir bağlam anahtarı nedeniyle kesilemez veya sorgulanamaz

2) Tutarlılık yani okuma / yazma op sırasının birden çok eşzamanlı ortam arasında aynı olduğu görülmelidir - dişler, makineler vb.

uçucu, yukarıdakilerin hiçbirine uymaz - veya daha özel olarak, uçucunun nasıl davranması gerektiğine dair c veya c ++ standardı yukarıdakilerin hiçbirini içermez.

Bazı derleyiciler (intel Itanium derleyici gibi) eşzamanlı erişim güvenli davranışın bazı unsurlarını (örneğin bellek çitleri sağlayarak) uygulamaya çalıştıklarından, uygulamada daha da kötüdür, ancak derleyici uygulamaları arasında herhangi bir tutarlılık yoktur ve ayrıca standart bunu gerektirmez ilk etapta uygulama.

Bir değişkeni uçucu olarak işaretlemek, her seferinde önbellek performansınızı uçurduğunuzda kodunuzu yavaşlatan değeri her seferinde temizlemeye ve bellekten temizlemeye zorladığınız anlamına gelir.

c # ve java AFAIK bunu 1) ve 2) 'ye uçucu bir şekilde yapıştırarak bunu giderir, ancak c / c ++ derleyicileri için aynı şey söylenemez, bu yüzden temelde uygun gördüğünüz gibi yapın.

Konuyla ilgili biraz daha derinlemesine (tarafsız olmamakla birlikte) tartışma için okunan bu


3
+1 - garantili atomisite, eksik olduğum şeyin bir başka parçasıydı. Bir int yüklemesinin atomik olduğunu varsayıyordum, böylece yeniden siparişi önleyen uçucu okuma tarafında tam çözüm sağladı. Sanırım çoğu mimaride iyi bir varsayım, ama bir garanti değil.
Michael Ekstrand

Bireysel ne zaman okunabilir ve belleğe yazılabilir ve atomik değildir? Herhangi bir faydası var mı?
batbrat

5

Comp.programming.threads SSS'nin Dave Butenhof tarafından klasik bir açıklaması var :

S56: Paylaşılan değişkenleri neden VOLATILE olarak bildirmem gerekiyor?

Bununla birlikte, hem derleyici hem de iş parçacığı kitaplığının kendi özelliklerini yerine getirdiği durumlar hakkında endişeliyim. Uygun bir C derleyicisi, CPU iş parçacığından iş parçacığına geçtikçe kaydedilen ve geri yüklenen bir yazmacıya bazı paylaşılan (kalıcı) değişkenleri global olarak tahsis edebilir. Her iş parçacığının bu paylaşılan değişken için kendi özel değeri olacaktır, bu da paylaşılan bir değişkenten istediğimiz şey değildir.

Derleyici değişkenin ilgili kapsamları ve pthread_cond_wait (veya pthread_mutex_lock) fonksiyonları hakkında yeterli bilgiye sahipse, bu bir bakıma doğrudur. Uygulamada, çoğu derleyici, harici bir işleve yapılan bir çağrıda küresel verilerin kayıt kopyalarını tutmaya çalışmaz, çünkü rutinin bir şekilde verilerin adresine erişip erişemeyeceğini bilmek çok zordur.

Bu nedenle evet, ANSI C'ye kesinlikle (ama çok agresif) uyan bir derleyicinin uçucu olmayan çoklu iş parçacıklarıyla çalışmayabileceği doğrudur. Ama birisinin düzelmesi daha iyi olur. Çünkü POSIX bellek tutarlılık garantisi vermeyen herhangi bir SİSTEM (pragmatik olarak çekirdek, kitaplıklar ve C derleyicisinin bir kombinasyonu) POSIX standardına UYGUN DEĞİLDİR. Dönemi. POSIX yalnızca POSIX senkronizasyon işlevlerinin gerekli olmasını gerektirdiğinden, sistem doğru davranış için paylaşılan değişkenlerde uçucu kullanmanızı GEREKTİRMEZ.

Dolayısıyla, programınız uçucu kullanmadığınız için bozulursa, bu bir HATA. C'deki bir hata veya thread kütüphanesindeki bir hata veya çekirdekteki bir hata olmayabilir. Ancak bu bir SİSTEM hatasıdır ve bu bileşenlerden biri veya daha fazlası bunu düzeltmek için çalışmalıdır.

Uçucu kullanmak istemezsiniz, çünkü herhangi bir fark yarattığı herhangi bir sistemde, uygun bir kalıcı değişkenten çok daha pahalı olacaktır. (ANSI C, her ifadedeki uçucu değişkenler için "sıra noktaları" gerektirirken, POSIX bunları yalnızca senkronizasyon işlemlerinde gerektirir - yoğun işlem kullanan bir iş parçacığı uygulaması, uçucu kullanarak önemli ölçüde daha fazla bellek etkinliği görür ve sonuçta bellek etkinliği gerçekten seni yavaşlatır.)

/ --- [Dave Butenhof] ----------------------- [butenhof@zko.dec.com] --- \
| Dijital Ekipman Şirketi 110 Spit Brook Rd ZKO2-3 / Q18 |
| 603.881.2218, FAKS 603.881.0120 Nashua NH 03062-2698 |
----------------- [Eşzamanlılık Yoluyla Daha İyi Yaşam] ---------------- /

Bay Butenhof, bu usenet yazısında aynı zeminin çoğunu kapsıyor :

"Uçucu" kullanımı, doğru bellek görünürlüğü veya evreler arasında senkronizasyon sağlamak için yeterli değildir. Muteks kullanımı yeterlidir ve çeşitli taşınabilir olmayan makine kodu alternatiflerine başvurmak dışında (veya önceki yazımda açıklandığı gibi, uygulanması çok daha zor olan POSIX bellek kurallarının daha ince sonuçları hariç), muteks GEREKLİDİR.

Bu nedenle, Bryan'ın açıkladığı gibi, uçucu kullanımı, derleyicinin yararlı ve istenen optimizasyonları önlemesini sağlamaktan başka bir şey yapmaz ve kodun "iş parçacığı güvenli" olmasına yardımcı olmaz. Tabii ki, "geçici" olarak istediğiniz herhangi bir şeyi beyan edebilirsiniz - sonuçta yasal bir ANSI C depolama özelliği. Sadece sizin için herhangi bir iş parçacığı senkronizasyon sorunlarını çözmek için beklemeyin.

C ++ için eşit derecede geçerli olan her şey.


Bağlantı koptu; artık alıntı yapmak istediklerinize işaret etmiyor gibi görünüyor. Metin olmadan, anlamsız bir cevap.
jww

3

Tüm bu "uçucu" yapıyor: "Hey derleyici, üzerinde herhangi bir YEREL TALİMATLAR olmasa bile, herhangi bir ANINDA (herhangi bir saat tik) bu değişken AT değişebilir. Bu değeri bir kayıtta önbelleğe ALMAYIN."

İşte bu. Derleyiciye değerinizin uçucu olduğunu söyler - bu değer herhangi bir anda harici mantık (başka bir iş parçacığı, başka bir işlem, Çekirdek, vb.) İle değiştirilebilir. Yalnızca bir kayıttaki bir değeri sessizce EVER önbelleğine güvenli olmayan bir şekilde önbelleğe alacak derleyici optimizasyonlarını bastırmak için az çok vardır.

"Dr. Dobbs" gibi çok iş parçacıklı programlama için geçici olarak her derde deva olarak savunulan makalelerle karşılaşabilirsiniz. Yaklaşımı tamamen haktan yoksun değildir, ancak bir nesnenin kullanıcılarını, diğer kapsülleme ihlalleriyle aynı sorunlara sahip olma eğiliminde olan iplik güvenliğinden sorumlu hale getirmenin temel kusuruna sahiptir.


3

Eski C standardıma göre, “Uçucu nitelikte bir türe sahip bir nesneye erişimi oluşturan şey uygulama tanımlıdır” . Bu yüzden C derleyici yazarları , "uçucu" anlamına gelen "çok işlemli bir ortamda güvenli iş parçacığı erişimi " olmasını seçmiş olabilirler . Ama yapmadılar.

Bunun yerine, kritik bir bölüm iş parçacığını çok çekirdekli çok işlemli paylaşılan bellek ortamında güvenli hale getirmek için gereken işlemler, yeni uygulama tanımlı özellikler olarak eklendi. Ve, "uçucu" bir çok işlemli ortamda atomik erişim ve erişim sıralaması sağlama gereksiniminden kurtulan derleyici yazarları, tarihsel uygulamaya bağlı "uçucu" anlambilimine göre kod azaltımına öncelik verdiler.

Bu, yeni derleyicilerle yeni donanım üzerinde çalışmayan kritik kod bölümleri etrafındaki "geçici" semaforlar gibi şeylerin eski donanımlarla eski derleyicilerle çalışmış olabileceği ve eski örneklerin bazen yanlış olmadığı, sadece eski olduğu anlamına gelir.


Eski örnekler, programın düşük seviyeli programlamaya uygun kaliteli derleyiciler tarafından işlenmesini gerektiriyordu. Ne yazık ki, "modern" derleyiciler, Standardın "uçucu" işlemeyi faydalı bir şekilde işleme koymalarını gerektirmediği gerçeğini kabul etmişlerdir. uygun olan ancak işe yaramayacak kadar düşük kalitede uygulamaları yasaklamak için çaba gösterme, ancak hiçbir şekilde popüler hale gelen düşük kaliteli ama uygun derleyicileri göz
ardı etmiyor

Çoğu platformda, volatilebir kişinin bir işletim sistemini donanıma bağımlı ancak derleyiciden bağımsız bir şekilde yazmasına izin vermek için ne yapılması gerektiğini anlamak oldukça kolay olacaktır. Programcıların volatilegerektiği gibi iş yapmak yerine uygulamaya bağlı özellikler kullanmasını istemek, bir standarda sahip olma amacını baltalar.
supercat
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.