Çoklu iş parçacığı ile uçucu ne zaman kullanılır?


131

Genel bir değişkene erişen iki iş parçacığı varsa, birçok öğretici, derleyicinin değişkeni bir kayıtta önbelleğe almasını önlemek için değişkeni uçucu hale getirdiğini ve dolayısıyla doğru şekilde güncellenmediğini söyler. Bununla birlikte, her ikisinin de paylaşılan bir değişkene erişmesi, muteks aracılığıyla koruma gerektiren bir şey değil mi? Ancak bu durumda, iş parçacığı kilitleme ile muteksin serbest bırakılması arasında kod, değişkene yalnızca bir iş parçacığının erişebildiği kritik bir bölümdedir, bu durumda değişkenin uçucu olması gerekmez?

Öyleyse, çok iş parçacıklı bir programda uçucu maddenin kullanımı / amacı nedir?


3
Bazı durumlarda, muteks tarafından korunmak istemezsiniz / buna ihtiyaç duymazsınız.
Stefan Mai

4
Bazen bir yarış durumuna sahip olmak iyidir, bazen değildir. Bu değişkeni nasıl kullanıyorsunuz?
David Heffernan

3
@David: Yarış yapmanın "iyi" olduğu bir örnek, lütfen?
John Dibling

6
@John İşte gidiyor. Bir dizi görevi işleyen bir çalışan iş parçacığınız olduğunu hayal edin. Çalışan iş parçacığı, bir görevi her bitirdiğinde bir sayacı artırır. Ana iş parçacığı periyodik olarak bu sayacı okur ve kullanıcıyı ilerleme haberleri ile günceller. Sayaç, yırtılmayı önlemek için düzgün şekilde hizalandığı sürece, erişimi senkronize etmeye gerek yoktur. Bir ırk olmasına rağmen iyi huyludur.
David Heffernan

5
@John Bu kodun çalıştığı donanım, hizalanmış değişkenlerin yırtılmaya maruz kalmayacağını garanti eder. Eğer işçi, okuyucu okurken n'yi n + 1'e güncelliyorsa, okuyucu n veya n + 1 almasını umursamaz. Yalnızca ilerleme raporlaması için kullanıldığından önemli kararlar alınmayacaktır.
David Heffernan

Yanıtlar:


168

Kısa ve hızlı yanıt : volatileplatformdan bağımsız, çok iş parçacıklı uygulama programlaması için (neredeyse) işe yaramaz. Herhangi bir senkronizasyon sağlamaz, bellek çitleri oluşturmaz ve işlemlerin yürütülme sırasını sağlamaz. İşlemleri atomik yapmaz. Kodunuzu sihirli bir şekilde güvenli hale getirmez. volatiletüm C ++ 'da en çok yanlış anlaşılan tesis olabilir. Daha fazla bilgi için buna , buna ve buna bakın.volatile

Öte yandan, volatileçok açık olmayabilecek bazı kullanımları var. constDerleyicinin, bazı paylaşılan kaynaklara korumasız bir şekilde erişirken nerede hata yaptığınızı size göstermesine yardımcı olmak için kullanılanla aynı şekilde kullanılabilir . Bu kullanım Alexandrescu tarafından bu makalede tartışılmaktadır . Bununla birlikte, bu temelde C ++ tipi sistemi, genellikle bir yaratıcılık olarak görülen ve Tanımsız Davranışı uyandırabilecek bir şekilde kullanmaktır.

volatileözellikle bellek eşlemeli donanım, sinyal işleyicileri ve setjmp makine kodu talimatı ile arabirim oluştururken kullanılmak üzere tasarlanmıştır. Bu, volatilenormal uygulama düzeyinde programlama yerine doğrudan sistem düzeyinde programlamaya uygulanabilir hale getirir .

2003 C ++ Standardı volatile, değişkenler üzerinde her türlü Acquire veya Release semantiğinin uygulandığını söylemez . Aslında, Standart, çok iş parçacıklı tüm konularda tamamen sessizdir. Ancak, belirli platformlar, Acquire ve Release semantiğini volatiledeğişkenler üzerinde uygular .

[C ++ 11 için güncelleme]

C ++ 11 Standart şimdi yapar , alındı bellek modeli ve dilt doğrudan çoklu kullanım ve bir platformdan bağımsız bir şekilde başa kütüphane imkanları sağlamaktadır. Ancak semantiği volatilehala değişmedi. volatilehala bir senkronizasyon mekanizması değildir. Bjarne Stroustrup, TCPPPL4E'de olduğu kadar:

volatileDoğrudan donanımla ilgilenen düşük seviyeli kod haricinde kullanmayın .

volatileBellek modelinde özel bir anlamı olduğunu varsaymayın . O değil. Daha sonraki bazı dillerde olduğu gibi, bir senkronizasyon mekanizması değildir. Senkronizasyonu almak için atomic, a mutexveya a kullanın condition_variable.

[/ Güncellemeyi bitir]

Yukarıdakilerin tümü, 2003 Standardında (ve şimdi 2011 Standardında) tanımlanan C ++ dilinin kendisini uygular. Ancak bazı belirli platformlar, ne işe yaradığına ek işlevler veya kısıtlamalar ekler volatile. Örneğin, MSVC 2010'da (en azından) edinimi ve Yayın semantik do üzerinde belirli işlemler için geçerli volatiledeğişkenler. MSDN'den :

Eniyileştirme sırasında, derleyicinin uçucu nesnelere yapılan referansların yanı sıra diğer genel nesnelere yapılan referanslar arasında sıralamayı sürdürmesi gerekir. Özellikle,

Uçucu bir nesneye yazma (uçucu yazma) Release semantiğine sahiptir; komut dizisindeki uçucu bir nesneye yazmadan önce meydana gelen global veya statik bir nesneye bir referans, derlenmiş ikili dosyadaki uçucu yazma işleminden önce gerçekleşecektir.

Uçucu bir nesnenin okunması (uçucu okuma) Acquire semantiğine sahiptir; Komut dizisindeki geçici belleğin okunmasından sonra meydana gelen global veya statik bir nesneye bir referans, derlenmiş ikili dosyadaki uçucu okumadan sonra gerçekleşecektir.

Bununla birlikte, yukarıdaki bağlantıyı izlerseniz, yorumlarda anlambilim edinme / bırakma anlamının bu durumda gerçekten geçerli olup olmadığına dair bazı tartışmalar olduğunu not edebilirsiniz .


19
Bir parçam, cevabın ve ilk yorumun küçümseyici tonu nedeniyle bunu olumsuz oylamak istiyor. "uçucu işe yaramaz", "manuel bellek ayırma işe yaramaz" anlamına gelir. Çok iş parçacıklı bir programı onsuz yazabiliyorsanız, iş parçacığı kitaplıklarını uygulayan volatileinsanların omuzlarında durmuş olmanızdır volatile.
Ben Jackson

20
@Ben sırf inançlarınıza meydan okuyan bir şey onu küçümseyici yapmaz
David Heffernan

39
@Ben: hayır, ne okumak volatileaslında yapar C ++. @ John'un söylediği doğru , hikayenin sonu. Uygulama kodu ile kütüphane kodu veya "sıradan" ile "tanrı gibi her şeyi bilen programcılar" ile ilgisi yoktur. volatileiş parçacıkları arasında senkronizasyon için gereksiz ve yararsızdır. İş parçacığı kitaplıkları volatile; yine de platforma özgü ayrıntılara güvenmek zorundadır ve bunlara güvendiğinizde artık ihtiyacınız kalmaz volatile.
jalf

6
@jalf: "uçucu, iş parçacıkları arasında senkronizasyon için gereksiz ve yararsızdır" (söylediğiniz şey), "uçucu çok iş parçacıklı programlama için işe yaramaz" ile aynı şey değildir (John'un cevabında söylediği şey budur). % 100

4
@GMan: Yararlı olan her şey yalnızca belirli bir dizi gereksinim veya koşul altında yararlıdır. Uçucu, katı koşullar altında çok iş parçacıklı programlama için kullanışlıdır (ve bazı durumlarda, alternatiflerden daha iyi olabilir (daha iyi bir tanım için). "Bunu görmezden gelin ve .." diyorsunuz ama volatile'nin çoklu iş parçacığı için yararlı olduğu durum hiçbir şeyi göz ardı etmez. Asla iddia etmediğim bir şey uydurdun. Evet, uçucu maddenin faydası sınırlıdır, ancak mevcuttur - ancak hepimiz bunun senkronizasyon için yararlı OLMADIĞI konusunda hemfikir olabiliriz.

31

(Editörün notu: C ++ 11'de volatilebu iş için doğru araç değil ve hala veri yarış UB'si var. Bunu UB olmadan yapmak için yükler / depolar std::atomic<bool>ile kullanın std::memory_order_relaxed. Gerçek uygulamalarda aynı asm ile derlenecektir volatile. daha ayrıntılı bir yanıt ve ayrıca zayıf sıralı belleğin bu kullanım durumu için bir sorun olabileceğine dair yorumlardaki yanlış anlamalara değiniyor: tüm gerçek dünya CPU'ları tutarlı bir paylaşılan belleğe sahip olduğundan gerçek C ++ uygulamalarında bunun için volatileçalışacak . Yapma.

Yorumlarda bazı tartışma diğer kullanım senaryoları bahsediyor gibi görünüyor ediyorum rahat atomics daha güçlü bir şeye ihtiyacım var. Bu cevap zaten volatilesize hiçbir sıralama vermediğini işaret ediyor.)


Uçucu bazen aşağıdaki nedenden dolayı yararlıdır: bu kod:

/* global */ bool flag = false;

while (!flag) {}

gcc tarafından şu şekilde optimize edilmiştir:

if (!flag) { while (true) {} }

Bu, bayrak diğer iş parçacığı tarafından yazılırsa açıkça yanlıştır. Bu optimizasyon olmadan senkronizasyon mekanizmasının muhtemelen çalıştığını unutmayın (diğer koda bağlı olarak bazı bellek engellerine ihtiyaç duyulabilir) - 1 üretici - 1 tüketici senaryosunda mutekse gerek yoktur.

Aksi takdirde, uçucu anahtar kelime kullanılamayacak kadar tuhaftır - herhangi bir bellek sıralaması, hem geçici hem de uçucu olmayan erişimleri garanti etmez ve herhangi bir atomik işlem sağlamaz - yani, devre dışı bırakılmış kayıt önbelleği dışında geçici anahtar kelimeli derleyiciden yardım almazsınız .


4
Hatırlarsam, C ++ 0x atomic, birçok insanın uçucu tarafından (yanlış olarak) yapıldığına inandığı şeyi doğru bir şekilde yapmak içindir.
David Heffernan

14
volatilebellek erişimlerinin yeniden düzenlenmesini engellemez. volatileerişimler birbirlerine göre yeniden sıralanmazlar, ancak nesne olmayanlara göre yeniden sıralama konusunda hiçbir garanti volatilevermezler ve bu nedenle temelde bayraklar kadar yararsızdırlar.
jalf

14
@Ben: Sanırım baş aşağı anladınız. "Uçucu yararsızdır" kalabalık, uçucu maddenin yeniden düzenlemeye karşı korumadığı basit gerçeğine dayanır , bu da eşzamanlama için tamamen yararsız olduğu anlamına gelir. Diğer yaklaşımlar da eşit derecede yararsız olabilir (bahsettiğiniz gibi, bağlantı zamanı kod optimizasyonu, derleyicinin, derleyicinin kara kutu gibi davranacağını varsaydığınız koda göz atmasına izin verebilir), ancak bu volatile,.
jalf

15
@jalf: Arch Robinson tarafından yazılan makaleye bakın (bu sayfada başka bir yerde bağlantılıdır), 10. yoruma ("Spud" tarafından yazılmıştır). Temel olarak, yeniden sıralama kodun mantığını değiştirmez. Gönderilen kod, bir görevi iptal etmek için bayrağı kullanır (görevin tamamlandığını belirtmek yerine), bu nedenle görevin koddan önce veya sonra iptal edilip edilmediğinin önemi yoktur (örn: while (work_left) { do_piece_of_work(); if (cancel) break;}iptal döngü içinde yeniden sıralanırsa, mantık hala geçerli. Benzer şekilde çalışan bir kod parçam vardı: ana iş parçacığı sonlandırmak istiyorsa, diğer iş parçacıkları için bayrağı ayarlıyor, ancak yapmıyor ...

15
... diğer iş parçacıkları, bayrak ayarlandıktan makul bir süre sonra olduğu sürece, sona ermeden önce çalışma döngülerinin fazladan birkaç yinelemesini yapsalar da önemli. Tabii ki, bu, düşünebildiğim ve oldukça niş olan TEK kullanımdır (ve uçucu bir değişkene yazmanın değişikliği diğer iş parçacıkları için görünür kılmadığı platformlarda çalışmayabilir, ancak en azından x86 ve x86-64'te bu İşler). Çok iyi bir neden olmadan kimseye bunu gerçekten yapmasını kesinlikle tavsiye etmem, sadece "uçucu çok iş parçacıklı kodda ASLA yararlı değildir" gibi genel bir ifadenin% 100 doğru olmadığını söylüyorum.

16

C ++ 11'de normalde asla volatilediş açma için kullanmayın , yalnızca MMIO için

Ancak TL: DR, mo_relaxeduyumlu önbelleklere sahip donanımda atomik gibi "çalışır" (yani her şey); derleyicilerin değişkenleri kayıtlarda tutmasını durdurmak yeterlidir. atomicatomiklik veya iş parçacığı arası görünürlük oluşturmak için bellek engellerine ihtiyaç duymaz, yalnızca geçerli iş parçacığının, bu iş parçacığının farklı değişkenlere erişimleri arasında sıralama oluşturmak için bir işlemden önce / sonra beklemesini sağlamak için. mo_relaxedhiçbir engele ihtiyaç duymaz, sadece yükleyin, depolayın veya RMW.

C ++ 11'den önceki kötü eski günlerde kendi atomlarınızı volatile(ve engelleri için satır içi) yuvarlamak için , bazı şeyleri çalıştırmanın tek iyi yolu buydu . Ancak uygulamaların nasıl çalıştığına dair birçok varsayıma bağlıydı ve hiçbir standart tarafından garanti edilmedi.std::atomicvolatile

Örneğin, Linux çekirdeği hala kendi elle çevrilmiş atomlarını kullanıyor volatile, ancak yalnızca birkaç özel C uygulamasını (GNU C, clang ve belki ICC) destekliyor. Bunun nedeni kısmen GNU C uzantıları ve satır içi asm sözdizimi ve anlambilim, ama aynı zamanda derleyicilerin nasıl çalıştığına dair bazı varsayımlara bağlı olmasıdır.

Yeni projeler için neredeyse her zaman yanlış seçimdir; Eğer kullanabilirsiniz std::atomic(ile std::memory_order_relaxedsizinle olabilir aynı verimli makine kodu yayması için bir derleyici almak için) volatile. diş açma amaçlı obsoletes std::atomicile . mo_relaxedvolatile(belki bazı derleyicilerde eksik optimizasyon hatalarınınatomic<double> üstesinden gelmek dışında .)

İç uygulama std::atomic(gcc ve clang gibi) ana akım derleyici gelmez üzerinde değil sadece kullanmak volatileiçten; derleyiciler doğrudan atomik yük, depolama ve RMW yerleşik işlevlerini ortaya çıkarır. (örneğin , "düz" nesneler üzerinde çalışan GNU C __atomicyerleşikleri .)


Uçucu pratikte kullanılabilir (ama yapmayın)

Bununla birlikte, CPU'ların nasıl çalıştığı (tutarlı önbellekler) ve nasıl çalışması gerektiğine dair paylaşılan varsayımlar nedeniyle, gerçek CPU'larda mevcut tüm (?) C ++ uygulamalarında volatilebir exit_nowbayrak gibi şeyler için pratikte kullanılabilir volatile. Ama çok fazla değil ve tavsiye edilmiyor . Bu cevabın amacı, mevcut CPU'ların ve C ++ uygulamalarının gerçekte nasıl çalıştığını açıklamaktır. Bunu umursamıyorsanız, tüm bilmeniz gereken, iş parçacığı için std::atomicmo_relaxed obsoletes olmasıdır volatile.

(ISO C ++ standardı bu konuda oldukça belirsizdir, sadece volatileerişimlerin optimize edilmeden değil, kesinlikle C ++ soyut makinenin kurallarına göre değerlendirilmesi gerektiğini söyler. Gerçek uygulamaların C ++ adres alanını modellemek için makinenin bellek adres alanını kullandığı göz önüne alındığında, bu, volatileokumaların ve atamaların bellekteki nesne temsiline erişmek için talimatları yüklemek / depolamak için derlenmesi gerektiği anlamına gelir .)


Başka bir yanıtın da işaret ettiği gibi, exit_nowbayrak, herhangi bir senkronizasyon gerektirmeyen basit bir iş parçacığı arası iletişim durumudur : dizi içeriklerinin hazır olduğunu veya buna benzer herhangi bir şeyi yayınlamaz. Başka bir ileti dizisindeki optimize edilmemiş bir yük tarafından hemen fark edilen bir mağaza.

    // global
    bool exit_now = false;

    // in one thread
    while (!exit_now) { do_stuff; }

    // in another thread, or signal handler in this thread
    exit_now = true;

Uçucu veya atomik olmadan, sanki kuralı ve hiçbir veri yarışının olmadığı varsayımı, bir derleyicinin onu sonsuz bir döngüye girmeden (veya girmeden) önce bayrağı yalnızca bir kez kontrol eden bir asm olarak optimize etmesine izin verir . Gerçek derleyiciler için gerçek hayatta olan tam olarak budur. (Ve genellikle do_stuffdöngü asla çıkmadığı için çoğunu optimize edin, bu nedenle sonucu kullanmış olabilecek daha sonraki herhangi bir koda, döngüye girersek erişilemez).

 // Optimizing compilers transform the loop into asm like this
    if (!exit_now) {        // check once before entering loop
        while(1) do_stuff;  // infinite loop
    }

Çok iş parçacıklı program optimize edilmiş modda kaldı, ancak normal olarak -O0'da çalışıyor, bunun x86-64'te GCC ile tam olarak nasıl gerçekleştiğinin bir örneğidir (GCC'nin asm çıktısının açıklamasıyla). Ayrıca MCU programlama - C ++ O2 optimizasyonu bozulurken elektronik devrelerdeki.SE başka bir örnek gösteriyor.

Normalde , genel değişkenler de dahil olmak üzere, CSE'nin ve yükleri döngüden kaldıran agresif optimizasyonlar istiyoruz .

C ++ 11'den önce,volatile bool exit_now bunun amaçlandığı gibi çalışmasını sağlamanın bir yolu vardı (normal C ++ uygulamalarında). Ancak C ++ 11'de, veri yarışı UB hala geçerlidir, volatilebu nedenle HW tutarlı önbellekleri varsayılsa bile her yerde çalışması ISO standardı tarafından garanti edilmez .

Daha geniş tipler için volatileyırtılma eksikliği garantisi vermediğini unutmayın. boolNormal uygulamalarda sorun olmadığı için burada bu ayrımı görmezden geldim . Ancak bu aynı zamanda volatile, gevşetilmiş atomik ile eşdeğer olmak yerine neden hala veri yarışına UB'ye tabi olduğunun bir parçası .

"Amaçlandığı gibi" ifadesinin, iş parçacığının exit_nowdiğer iş parçacığının gerçekten çıkmasını beklediği anlamına gelmediğini unutmayın . Veya exit_now=truebu iş parçacığındaki sonraki işlemlere devam etmeden önce uçucu deponun küresel olarak görünür olmasını bile beklediğini . ( atomic<bool>varsayılan mo_seq_cst, en azından sonraki seq_cst yüklenmeden önce beklemesini sağlar. Birçok ISA'da, mağazadan sonra tam bir bariyer elde edersiniz).

C ++ 11, aynı şeyi derleyen UB olmayan bir yol sağlar

"Çalışmaya devam et" veya "şimdi çık" bayrağı std::atomic<bool> flag,mo_relaxed

kullanma

  • flag.store(true, std::memory_order_relaxed)
  • while( !flag.load(std::memory_order_relaxed) ) { ... }

size alacağınız aynı asmi (pahalı engel talimatları olmadan) verecektir volatile flag.

Yırtılmanın yanı sıra atomic, size UB olmadan bir iş parçacığında depolama ve başka bir iş parçacığında yükleme yeteneği sağlar, böylece derleyici yükü bir döngüden kaldıramaz. (Atomik olmayan uçucu olmayan nesneler için istediğimiz agresif optimizasyonlara izin veren, veri yarışı olmayan UB varsayımıdır.) Bu özellik, saf yükler ve saf depolar için atomic<T>olanla hemen hemen aynıdır volatile.

atomic<T>Ayrıca yapmak +=ve böylece (eğer bir atom RMW istemiyorsanız, yerel bir geçici ile kod yazmak geçici, işletmek, ardından ayrı atom deposuna bir atom yükü önemli ölçüde daha pahalı.) atomik RMW operasyonları içine üzerinde.

seq_cstAlacağınız varsayılan siparişle while(!flag), aynı zamanda sipariş garantileri de ekler. atomik olmayan erişimler ve diğer atomik erişimler.

(Teorik olarak, ISO C ++ standart atomics ait derleme zamanı optimizasyonu ekarte etmez. Ama pratikte derleyiciler yok Bunda sorun olmaz denetime yolu yoktur çünkü. Birkaç durumlar vardır bile volatile atomic<T>olmayabilir Şimdi derleyiciler yok öylesine için, derleyiciler optimize yapsam atomics optimizasyonu üzerinde yeterli kontrol olun. Bkz Neden derleyiciler gereksiz std :: atom yazıyor? birleştirme yok wg21 / p0062 kullanılmasına karşı önerir not volatile atomicoptimizasyonu karşı koruma için bu koda atomik).


volatile aslında bunun için gerçek CPU'larda çalışıyor (ama yine de kullanmıyor)

zayıf sıralı bellek modellerinde bile (x86 olmayan) . Ama aslında kullanmayın, bunun yerine atomic<T>ile mo_relaxedkullanın !! Bu bölümün amacı, gerçek CPU'ların nasıl çalıştığına dair yanlış anlamaları gerekçelendirmek değil volatile. Kilitsiz kod yazıyorsanız, muhtemelen performansı önemsiyorsunuzdur. Önbellekleri ve iş parçacıkları arası iletişimin maliyetlerini anlamak, genellikle iyi performans için önemlidir.

Gerçek CPU'ların uyumlu önbellekleri / paylaşılan hafızaları vardır: bir çekirdekten bir depo küresel olarak görünür hale geldikten sonra, başka hiçbir çekirdek eski bir değer yükleyemez . (Ayrıca bkz.Mits Programcıları, seq_cst bellek sırasına sahip C ++ 'ya eşdeğer Java uçucularından bahseden CPU Önbellekleri hakkında atomic<T>inanırlar.)

Dediğimde yükü , ben hafızayı erişen bir asm talimatı anlamına gelir. Bu, bir volatileerişimin sağladığı şeydir ve atomik olmayan / uçucu olmayan bir C ++ değişkeninin ldeğerden r değerine dönüşümüyle aynı şey değildir . (örneğin local_tmp = flagveya while(!flag)).

Yenmeniz gereken tek şey, ilk kontrolden sonra hiç yeniden yüklenmeyen derleme zamanı optimizasyonlarıdır. Her yinelemedeki herhangi bir yükleme + kontrol, herhangi bir sıralama olmaksızın yeterlidir. Bu iş parçacığı ile ana iş parçacığı arasında senkronizasyon olmadan, deponun tam olarak ne zaman gerçekleştiğinden veya yükün sıralanmasından bahsetmek anlamlı değildir. döngüdeki diğer işlemler. Sadece bu ileti dizisine göründüğünde önemli olan şeydir. Exit_now bayrak setini gördüğünüzde çıkarsınız. Tipik bir x86 Xeon'daki çekirdekler arası gecikme, ayrı fiziksel çekirdekler arasında 40ns gibi bir şey olabilir .


Teoride: Uyumlu önbellekleri olmayan donanımda C ++ iş parçacıkları

Programcının kaynak kodda açık yıkamalar yapmasını gerektirmeden, sadece saf ISO C ++ ile bunun uzaktan verimli olabileceği bir yol görmüyorum.

Teoride, bunun gibi olmayan bir makinede bir C ++ uygulamasına sahip olabilirsiniz, bu da nesneleri diğer çekirdeklerdeki diğer iş parçacıkları için görünür kılmak için derleyici tarafından oluşturulan açık yıkamalar gerektirir. . (Veya okumalar için belki eski bir kopya kullanmamak için). C ++ standardı bunu imkansız kılmaz, ancak C ++ 'ın bellek modeli, tutarlı paylaşımlı bellekli makinelerde verimli olacak şekilde tasarlanmıştır. Örneğin, C ++ standardı "okuma-okuma tutarlılığı", "yazma-okuma tutarlılığı" vb. Hakkında bile konuşur. Standarttaki bir not bile donanımla bağlantıya işaret eder:

http://eel.is/c++draft/intro.races#19

[Not: Her iki işlem de gevşetilmiş yükler olsa bile, önceki dört tutarlılık gereksinimi, derleyicinin atomik işlemleri tek bir nesneye yeniden düzenlemesine etkili bir şekilde izin vermez. Bu, çoğu donanım tarafından sağlanan önbellek tutarlılığı garantisini C ++ atomik işlemler için kullanılabilir hale getirir.- son not]

Bir releasemağazanın yalnızca kendisini temizlemesi için bir mekanizma ve birkaç belirli adres aralığı yoktur: her şeyi senkronize etmesi gerekirdi, çünkü eğer alma-yükleri bu yayın deposunu görselerdi, diğer iş parçacıklarının okumak isteyebileceklerini bilemezdi (bir Yazma iş parçacığı tarafından yapılan atomik olmayan daha önceki işlemlerin artık okunmasının güvenli olduğunu garanti ederek iş parçacıkları arasında bir önce-olur ilişkisi kuran sürüm dizisi. Yayın deposundan sonra onlara daha fazla yazmadıkça ...) Veya derleyiciler sadece birkaç önbellek satırının temizlenmesi gerektiğini kanıtlamak için gerçekten akıllı olmak.

İlgili: mov + mfence NUMA'da güvenli mi? tutarlı paylaşılan bellek olmadan x86 sistemlerinin var olmaması hakkında ayrıntılara giriyor. Ayrıca ilgili: Aynı yüklemeler / depolar hakkında daha fazla bilgi için ARM üzerinde yeniden sıralama yükler ve depolar konuma .

Orada olan ben-olmayan tutarlı paylaşılan bellek ile kümeleri düşünüyorum, ama bunlar tek sistem görüntü makineleri değiliz. Her tutarlılık etki alanı ayrı bir çekirdek çalıştırır, bu nedenle tek bir C ++ programının iş parçacıkları üzerinde çalıştıramazsınız. Bunun yerine, programın ayrı örneklerini çalıştırırsınız (her birinin kendi adres alanı vardır: bir örnekteki işaretçiler diğerinde geçerli değildir).

Açık yıkamalar aracılığıyla birbirleriyle iletişim kurmalarını sağlamak için, programın hangi adres aralıklarının temizlenmesi gerektiğini belirtmesini sağlamak için tipik olarak MPI veya diğer ileti aktarım API'sini kullanırsınız.


Gerçek donanım, std::threadönbellek tutarlılığı sınırlarının ötesine geçmez :

Paylaşılan fiziksel adres alanına sahip ancak içte paylaşılabilir önbellek etki alanlarına sahip olmayan bazı asimetrik ARM yongaları mevcuttur . Yani tutarlı değil. (örneğin, açıklama iplik bir A8 çekirdek TI Sitara AM335x gibi Cortex-M3).

Ancak bu çekirdekler üzerinde farklı çekirdekler çalışır, her iki çekirdekte de iş parçacıkları çalıştırabilen tek bir sistem görüntüsü değil. std::threadTutarlı önbellekler olmadan CPU çekirdeklerinde iş parçacığı çalıştıran herhangi bir C ++ uygulamasından haberdar değilim .

Özel olarak ARM için, GCC ve clang, tüm iş parçacıklarının aynı iç paylaşılabilir etki alanında çalıştığını varsayarak kod üretir. Aslında, ARMv7 ISA kılavuzu diyor ki

Bu mimari (ARMv7), aynı işletim sistemini veya hiper yöneticiyi kullanan tüm işlemcilerin aynı Dahili Paylaşılabilir paylaşılabilirlik etki alanında olması beklentisiyle yazılmıştır.

Dolayısıyla, ayrı etki alanları arasında tutarlı olmayan paylaşılan bellek, yalnızca farklı çekirdekler altındaki farklı işlemler arasındaki iletişim için paylaşılan bellek bölgelerinin açık sisteme özgü kullanımı için bir şeydir.

Ayrıca bu derleyicidedmb ish (İç Paylaşılabilir bariyer) ve dmb sy(Sistem) bellek engellerini kullanan kod oluşturma hakkındaki bu CoreCLR tartışmasına da bakın .

Diğer ISA'lar için hiçbir C ++ uygulamasının std::threadtutarlı olmayan önbelleklere sahip çekirdeklerde çalışmadığını iddia ediyorum . Böyle bir uygulamanın olmadığına dair kanıtım yok, ancak bu pek olası görünmüyor. Bu şekilde çalışan belirli bir egzotik HW parçasını hedeflemediğiniz sürece, performans hakkındaki düşünceniz tüm iş parçacıkları arasında MESI benzeri önbellek tutarlılığını varsaymalıdır. (Tercihen atomic<T>doğruluğu garanti eden şekillerde kullanın !)


Tutarlı önbellekleri basitleştirir

Ancak tutarlı önbellekleri olan çok çekirdekli bir sistemde, bir yayın deposu uygulamak, yalnızca bu iş parçacığının mağazaları için önbelleğe kaydetme siparişi vermek anlamına gelir, herhangi bir açık temizleme işlemi yapmaz. ( https://preshing.com/20120913/acquire-and-release-semantics/ ve https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ ). (Ve bir alma-yükleme, diğer çekirdekte önbelleğe erişim siparişi vermek anlamına gelir).

Bir bellek bariyer talimatı, mevcut iş parçacığının yüklemelerini engeller ve / veya depolama tamponu boşalana kadar depolar; bu her zaman kendi başına olabildiğince hızlı gerçekleşir. ( Bir bellek engeli, önbellek tutarlılığının tamamlanmasını sağlıyor mu? Bu yanlış kanıyı giderir). Bu nedenle, sipariş vermeye ihtiyacınız yoksa, diğer konu başlıklarında anında görünürlük yeterlidir mo_relaxed. (Ve öyleysevolatile , ama bunu yapma.)

İşlemciler için C / C ++ 11 eşlemelerine de bakın

Eğlenceli gerçek: x86'da her asm deposu bir yayın deposu çünkü x86 bellek modeli temelde seq-cst artı bir depo tamponu (depo iletme ile).


Yarı ilişkili re: store buffer, global görünürlük ve tutarlılık: C ++ 11 çok az garanti verir. Çoğu gerçek ISA (PowerPC hariç), tüm iş parçacıklarının diğer iki iş parçacığı tarafından iki mağazanın görünüm sırasına karar verebileceğini garanti eder. (Resmi bilgisayar mimarisi bellek modeli terminolojisinde, bunlar "çok kopyalı atomiktir").

Başka bir yanlış anlama bellek çit asm talimatları diğer çekirdekler mağazalarımızı görmek için mağaza tampon temizlemek için gerekli olmasıdır hiç . Aslında depo tamponu her zaman kendisini olabildiğince hızlı boşaltmaya çalışır (L1d önbelleğine bağlanır), aksi takdirde doldurur ve yürütmeyi durdurur. Tam bir bariyerin / çitin yaptığı şey , mevcut iş parçacığını depo tamponu boşalıncaya kadar bekletmektir , böylece daha sonraki yüklerimiz , önceki mağazalarımızdan sonra küresel düzende görünür.

(x86 kuvvetle asm bellek modeli araçlarını sipariş etti o volatiledaha yakın size vererek sona erebilir x86 üzerinde mo_acq_relhala gerçekleşebilir olmayan atom değişkenlerle yeniden düzenlenmesi o derleme zamanında hariç. Ama en x86 dışındaki bellek modellerini bu yüzden zayıf-emretti volatileve relaxedyaklaşık vardır mo_relaxedizin verdiği kadar zayıf .)


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Samuel Liew

2
Harika yazı. Bu tam olarak aradığım şeydi ( tüm gerçekleri vererek ) sadece "tek bir küresel paylaşılan boole bayrağı için uçucu yerine atomik kullanın" diyen kapsamlı bir ifade yerine.
bernie

2
@bernie: Bunu, kullanmamanın önbellekteatomic aynı değişken için farklı değerlere sahip farklı iş parçacıkları olmasına yol açabileceğine dair tekrarlanan iddialar yüzünden hayal kırıklığına uğradıktan sonra yazdım . / facepalm. Önbellekte hayır, CPU kayıtlarında evet (atomik olmayan değişkenlerle); CPU'lar tutarlı önbellek kullanır. SO ile ilgili diğer soruların, CPU'ların nasıl çalıştığına dair yaygın yanlış kanılara ilişkin açıklamalarla dolu olmamasını dilerdim . (Çünkü bu, performansla ilgili nedenlerden dolayı anlaşılması yararlı bir şey ve ayrıca ISO C ++ atom kurallarının neden oldukları gibi yazıldığını açıklamaya yardımcı oluyor.)atomic
Peter Cordes

-1
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

bool checkValue = false;

int main()
{
    std::thread writer([&](){
            sleep(2);
            checkValue = true;
            std::cout << "Value of checkValue set to " << checkValue << std::endl;
        });

    std::thread reader([&](){
            while(!checkValue);
        });

    writer.join();
    reader.join();
}

Bir zamanlar uçuculuğun faydasız olduğuna inanan bir görüşmeci benimle Optimizasyon'un herhangi bir soruna yol açmayacağını ve ayrı önbellek hatlarına sahip farklı çekirdeklerden bahsettiğini ve tüm bunlardan bahsetti (tam olarak neye atıfta bulunduğunu gerçekten anlamadı). Fakat bu kod parçası g ++ üzerinde -O3 ile derlendiğinde (g ++ -O3 thread.cpp -lpthread) tanımsız davranış gösterir. Temel olarak, değer while kontrolünden önce ayarlanırsa iyi çalışır ve değilse değeri getirme zahmetine girmeden bir döngüye girer (aslında diğer iş parçacığı tarafından değiştirildi). Temel olarak, checkValue değerinin kayda yalnızca bir kez getirildiğine ve en yüksek optimizasyon seviyesinde bir daha asla kontrol edilmediğine inanıyorum. Getirmeden önce true olarak ayarlanmışsa, iyi çalışır ve değilse bir döngüye girer. Lütfen yanılıyorsam düzeltin.


4
Bunun ne alakası var volatile? Evet, bu kod UB'dir - ama volatileaynı zamanda UB'dir .
David Schwartz

-2

Uçucu ve muhtemelen kilitlenmeye ihtiyacınız var.

volatile, iyileştiriciye değerin eşzamansız olarak değişebileceğini söyler, böylece

volatile bool flag = false;

while (!flag) {
    /*do something*/
}

döngü boyunca her seferinde bayrağı okuyacaktır.

Optimizasyonu kapatırsanız veya her değişkeni uçucu hale getirirseniz, bir program aynı ancak daha yavaş davranacaktır. uçucu sadece 'Sadece okuduğunuzu ve ne yazdığını bildiğinizi biliyorum, ama okuyun dersem okuyun.

Kilitleme, programın bir parçasıdır. Bu arada, semaforları uyguluyorsanız, diğer şeylerin yanı sıra uçucu olmaları gerekir. (Denemeyin, zordur, muhtemelen biraz toplayıcıya veya yeni atomik malzemeye ihtiyaç duyacaktır ve zaten yapılmıştır.)


1
Ama bu ve diğer yanıttaki aynı örnek, beklemekle meşgul ve dolayısıyla kaçınılması gereken bir şey değil mi? Bu yapmacık bir örnekse, uydurulmamış gerçek hayat örnekleri var mı?
David Preston

7
@Chris: Meşgul bekleme bazen iyi bir çözümdür. Özellikle, yalnızca birkaç saat döngüsünü beklemek zorunda kalacağınızı düşünüyorsanız, iş parçacığını askıya alma şeklindeki çok daha ağır yaklaşımdan çok daha az ek yük taşır. Tabii ki, diğer yorumlarda da bahsettiğim gibi, bunun gibi örnekler kusurludur çünkü bayrağın koruduğu koda göre yeniden sıralanmayacağını varsayarlar ve böyle bir garanti verilmez. , volatilebu durumda bile gerçekten kullanışlı değil. Ancak meşgul bekleme, ara sıra faydalı bir tekniktir.
jalf

3
@richard Evet ve hayır. İlk yarı doğru. Ancak bu, yalnızca CPU ve derleyicinin değişken değişkenleri birbirine göre yeniden düzenlemesine izin verilmediği anlamına gelir. Uçucu bir A değişkenini okursam ve sonra uçucu bir değişken B'yi okursam, derleyicinin B'den önce A'yı okumak için garantili (CPU yeniden sıralama ile bile) bir kod yayınlaması gerekir.Ancak bu, tüm uçucu olmayan değişken erişimleri hakkında hiçbir garanti vermez. . Uçucu okuma / yazma işleminiz etrafında yeniden düzenlenebilirler. Dolayısıyla , programınızdaki her değişkeni uçucu hale
jalf

2
@ ctrl-alt-delor: volatile"Yeniden sıralama yok" demek bu değil. Bunun, mağazaların program sırasına göre küresel olarak (diğer konulara) görünür hale geleceği anlamına geldiğini umuyorsunuz . Yani ne var atomic<T>ile memory_order_releaseveya seq_cstverir. Ancak size volatile yalnızca derleme zamanı yeniden sıralama garantisi verir : her erişim, program sırasına göre asm'de görünecektir. Bir aygıt sürücüsü için kullanışlıdır. Ve geçerli çekirdek / iş parçacığı üzerindeki bir kesme işleyicisi, hata ayıklayıcı veya sinyal işleyici ile etkileşim için kullanışlıdır, ancak diğer çekirdeklerle etkileşim için değil.
Peter Cordes

1
volatilepratikte keep_runningburada yaptığınız gibi bir bayrağı kontrol etmek için yeterlidir : Gerçek CPU'larda her zaman manuel temizleme gerektirmeyen uyumlu önbellekler bulunur. Ama tavsiye için hiçbir neden yok volatileüzerinde atomic<T>olan mo_relaxed; aynı asm alacaksınız.
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.