C ++ 11'de bir StoreLoad bariyeri nasıl elde edilir?


13

Klasik bir sorunun bir varyantını çözen taşınabilir kod (Intel, ARM, PowerPC ...) yazmak istiyorum:

Initially: X=Y=0

Thread A:
  X=1
  if(!Y){ do something }
Thread B:
  Y=1
  if(!X){ do something }

hangi amaç hem ipler yapıyoruz bir durum kaçınmaktırsomething . (Hiçbir şey çalışmazsa sorun değil, bu tam olarak bir kez çalıştırılan bir mekanizma değildir.) Aşağıdaki gerekçemde bazı kusurlar görüyorsanız lütfen beni düzeltin.

memory_order_seq_cstAtomik stores ve loads ile hedefe şu şekilde ulaşabileceğimin farkındayım :

std::atomic<int> x{0},y{0};
void thread_a(){
  x.store(1);
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!x.load()) bar();
}

Bu, hedefe ulaşır, çünkü
{x.store(1), y.store(1), y.load(), x.load()}etkinliklerde program sırası "kenarları" ile anlaşması gereken tek bir toplam sipariş olmalıdır:

  • x.store(1) "TO içinde önce" y.load()
  • y.store(1) "TO içinde önce" x.load()

ve foo()çağrıldıysa, ek avantajımız var:

  • y.load() "değeri daha önce okur" y.store(1)

ve bar()çağrıldıysa, ek avantajımız var:

  • x.load() "değeri daha önce okur" x.store(1)

ve tüm bu kenarların bir araya getirilmesi bir döngü oluşturur:

x.store(1)"içindeki TO daha önce" y.load()"değeri" y.store(1)"den önce TO'da" "daha önce x.load()değer okuyor"x.store(true)

bu da emirlerin hiçbir döngüsünün olmaması gerçeğini ihlal ediyor.

Kasıtlı olarak standart olmayan terimleri "TO önce" ve "önceki değeri okur" gibi standart terimlerin aksine kullanıyorum happens-before, çünkü bu kenarların gerçekten bir happens-beforeilişki anlamına geldiği varsayımımın doğruluğu hakkında geri bildirim istiyorum. ve bu tür birleşik grafikteki döngü yasaktır. Ben bu konuda emin değilim. Ne biliyorum bu kod Intel gcc & clang ve ARM gcc doğru engelleri üretir


Şimdi, gerçek sorunum biraz daha karmaşık, çünkü "X" üzerinde hiçbir kontrole sahip değilim - bazı makroların, şablonların vb. Arkasında gizlenmiş ve daha zayıf olabilir. seq_cst

"X" in tek bir değişken mi yoksa başka bir kavram mı olduğunu bilmiyorum (örneğin hafif semafor veya muteks). Tüm bildiğim iki makro var set()ve check()böyle "sonra" başka bir iş parçacığı çağırdı check()döner öyle . (O olduğu da bilinmektedir ve evreli vardır ve veri yarış UB oluşturamazsınız.)trueset()setcheck

Yani kavramsal set()olarak "X = 1" check()gibi ve "X" gibi, ama varsa atomiklere doğrudan erişimim yok.

void thread_a(){
  set();
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!check()) bar();
}

Endişeliyim, bu set()dahili olarak uygulanabilir x.store(1,std::memory_order_release)ve / veya check()olabilir x.load(std::memory_order_acquire). Ya da varsayımsal olarak bir std::mutexiş parçacığının kilidinin açılması ve bir iş parçacığının açılması try_lock; ISO standardında std::mutexsadece seq_cst değil, sipariş alma ve bırakma garantisi vardır.

Eğer durum buysa, o zaman check()beden daha önce "yeniden sıralanabilir" ise y.store(true)( Alex'in PowerPC'de bunun olduğunu gösterdikleri cevabına bakınız ).
Şimdi bu olaylar dizisi mümkün olduğu için bu gerçekten kötü olurdu:

  • thread_b()önce x( 0) eski değerini yükler
  • thread_a() dahil her şeyi yürütür foo()
  • thread_b() dahil her şeyi yürütür bar()

Yani, hem foo()ve bar()ben önlemek zorunda olan çağırıldım. Bunu önlemek için seçeneklerim nelerdir?


Seçenek A

Depo Yük bariyerini zorlamaya çalışın. Bu, pratikte, Alexstd::atomic_thread_fence(std::memory_order_seq_cst); tarafından farklı bir cevapta açıklandığı gibi, test edilen tüm derleyiciler tam bir çit yayınladı:

  • x86_64: MFENCE
  • PowerPC: hwsync
  • Itanuim: mf
  • ARMv7 / ARMv8: dmb ish
  • MIPS64: senkronizasyon

Bu yaklaşımla ilgili sorun, std::atomic_thread_fence(std::memory_order_seq_cst)tam bellek bariyerine çevirmek zorunda C ++ kurallarında herhangi bir garanti bulamadı olmasıdır . Aslında, atomic_thread_fenceC ++ 'daki s kavramı, bellek bariyerlerinin montaj konseptinden farklı bir soyutlama düzeyinde görünmekte ve "atomik işlemin neyle senkronize olduğu" gibi şeylerle daha fazla ilgilenmektedir. Aşağıdaki uygulamanın hedefe ulaştığına dair herhangi bir teorik kanıt var mı?

void thread_a(){
  set();
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!y.load()) foo();
}
void thread_b(){
  y.store(true);
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!check()) bar();
}

Seçenek B

Y üzerinde okuma-değiştirme-yazma memory_order_acq_rel işlemlerini kullanarak senkronizasyonu elde etmek için Y üzerinde yaptığımız kontrolü kullanın:

void thread_a(){
  set();
  if(!y.fetch_add(0,std::memory_order_acq_rel)) foo();
}
void thread_b(){
  y.exchange(1,std::memory_order_acq_rel);
  if(!check()) bar();
}

Buradaki fikir, tek atom için erişimler (yani yböylece ya) Tüm gözlemciler üzerinde uzlaştıkları tek bir sipariş formu olmalı fetch_addöncedir exchangeveya tersi.

Eğer fetch_adddaha önce exchangeise "release" kısmı fetch_addile "acquire" kısmı ile senkronize edilir exchangeve böylece tüm yan etkileri set()kod yürütme görünür olması gerekir check(), bu yüzden bar()çağrılmaz.

Aksi takdirde, exchangeönce fetch_add, o fetch_addzaman görecek 1ve aramayacak foo(). Yani, her iki çağırmak mümkün değildir foo()ve bar(). Bu muhakeme doğru mu?


Seçenek C

Afet önlemek "kenarları" tanıtmak için kukla atomlar kullanın. Aşağıdaki yaklaşımı düşünün:

void thread_a(){
  std::atomic<int> dummy1{};
  set();
  dummy1.store(13);
  if(!y.load()) foo();
}
void thread_b(){
  std::atomic<int> dummy2{};
  y.store(1);
  dummy2.load();
  if(!check()) bar();
}

Buradaki sorunun atomicyerel olduğunu düşünüyorsanız, bunları küresel kapsama taşıdığınızı hayal edin, aşağıdaki muhakemede benim için önemli görünmüyor ve kasten bu kukla ne kadar komik olduğunu ortaya çıkaracak şekilde kodu yazdım1 ve dummy2 tamamen ayrıdır.

Neden Dünya'da bu işe yarayabilir? Peki, toplam sırası {dummy1.store(13), y.load(), y.store(1), dummy2.load()}program sırası "kenarları" ile tutarlı olması gereken tek bir toplam sırası olmalıdır :

  • dummy1.store(13) "TO içinde önce" y.load()
  • y.store(1) "TO içinde önce" dummy2.load()

(Bir seq_cst deposu + yükü, ayrı bir engelleme talimatının gerekli olmadığı AArch64 bile dahil gerçek ISA'larda olduğu gibi, StoreLoad dahil tam bir bellek bariyerinin C ++ eşdeğerini umarım oluşturur.)

Şimdi, dikkate almamız gereken iki durum var: ya toplam sırayla y.store(1)önce y.load()ya da sonra.

Eğer y.store(1)öncedir y.load()sonra foo()aradı ve güvenli olmayacaktır.

Daha y.load()önce ise y.store(1), daha önce program düzeninde bulunan iki kenarla birleştirerek şunu ortaya çıkarırız:

  • dummy1.store(13) "TO içinde önce" dummy2.load()

Şimdi, dummy1.store(13)etkilerini serbest bırakan set()ve dummy2.load()bir edinme işlemidir, bu yüzden check()etkilerini görmeli set()ve böylece bar()çağrılmayacak ve güvende olacağız.

Bunun check()sonuçlarını göreceğini düşünmek doğru set()mu? Çeşitli "kenarları" ("program sırası" aka Sıralı Önce, "toplam sipariş", "yayınlamadan önce", "edinme sonra") bu şekilde birleştirebilir miyim? Bu konuda ciddi şüphelerim var: C ++ kuralları aynı konumda mağaza ve yük arasındaki ilişkilerle "senkronize" ilişkileri hakkında konuşmak gibi görünüyor - burada böyle bir durum yok.

Biz sadece dava hakkında endişeli olduğunuzu Not dumm1.storeedilmektedir bilinen (diğer akıl yoluyla) önce olmak dummy2.loadseq_cst toplam sipariş. Dolayısıyla, aynı değişkene erişiyor olsaydı, yük saklanan değeri görür ve onunla senkronize olur.

(Atomik yüklerin ve depoların en az 1 yollu bellek bariyerlerine derlendiği (ve seq_cst işlemlerinin yeniden sıralanamayacağı uygulamalar için bellek bariyeri / yeniden sıralama nedeni: örneğin bir seq_cst deposu bir seq_cst yükünü geçemez) mağazalar sonra dummy2.loadkesinlikle başka iş parçacığı tarafından görülebilir hale sonrasında y.store . Ve benzer diğer iş, ... önce y.load.)


Https://godbolt.org/z/u3dTa8 adresinden Seçenekler A, B, C uygulamamla oynayabilirsiniz.


1
C ++ bellek modelinde herhangi bir StoreLoad yeniden sıralama kavramı yoktur, sadece senkronize olur ve daha önce olur. (Ve atomik olmayan nesneler üzerinde veri yarışları, asm aksine gerçek donanım için. Üzerinde UB) Bildiğim kadarıyla tüm gerçek uygulamalarda günü, std::atomic_thread_fence(std::memory_order_seq_cst)tam bir bariyere derleme yapar, ancak tüm konsept bir uygulama ayrıntı olduğundan size bulamazsınız standart herhangi bir söz. (CPU bellek modeller genellikle edilir reorerings sıralı kıvama göre izin verilen ne açısından tanımlandığı gibi 86 olan seq sabit + bir mağaza tamponu ağırlık / yönlendirme.)
Peter Cordes

@PeterCordes teşekkürler, yazımda net olmayabilirdim. "Seçenek A" bölümünde yazdıklarınızı iletmek istedim. Sorumun başlığının "StoreLoad" kelimesini kullandığını biliyorum ve "StoreLoad" tamamen farklı bir dünyadan gelen bir kavram. Benim sorunum bu kavram C ++ ile nasıl eşleştirilir. Veya doğrudan haritalandırılamazsa, o zaman ortaya koyduğum hedefe nasıl ulaşılır: her ikisinin de çağrılmasını önleyin foo()ve önleyin bar().
qbolec

1
compare_exchange_*Bir atomik bool üzerinde değerini değiştirmeden RMW işlemi yapmak için kullanabilirsiniz (sadece beklenen ve yeni değeri aynı değere ayarlayın).
mpoeter

1
@Fareanor ve qbolec: atomic<bool>vardır exchangeve compare_exchange_weak. İkincisi, CAS (doğru, doğru) veya yanlış, yanlış (denemeye) çalışarak bir sahte RMW yapmak için kullanılabilir. Başarısız olur veya değeri atomla değiştirir. (X86-64 asm'da, bu hile, lock cmpxchg16bgarantili atomik 16 bayt yükleri nasıl yaptığınızdır; verimsiz ama ayrı bir kilit almaktan daha az kötü.)
Peter Cordes

1
@PeterCordes evet Ne foo()de ne bar()denir olacağını biliyorum . Ben "sorun X var ama Y sorun var" tür cevaplar önlemek için, kodun birçok "gerçek dünya" unsurları getirmek istemiyordu. Ancak, eğer gerçekten arka plan katının ne olduğunu bilmek gerekiyorsa: set()gerçekten some_mutex_exit(), check()öyle try_enter_some_mutex(), y"bazı garsonlar var", foo()"kimseyi uyandırmadan çık", bar()"uyanmayı bekle" ... Ama reddediyorum Bu tasarımı burada tartış - gerçekten değiştiremem.
qbolec

Yanıtlar:


5

A ve B seçenekleri geçerli çözümlerdir.

  • Seçenek A: Bir seq-cst çitinin ne anlama geldiği önemli değildir, C ++ standardı hangi garantileri sağladığını açıkça tanımlar. Ben bu yazıda onları ortaya koydu: Ne zaman bir memory_order_seq_cst çit yararlıdır?
  • Seçenek B: evet, muhakemeniz doğru. Bazı nesnelerdeki tüm modifikasyonların tek bir toplam sırası (modifikasyon sırası) vardır.

Ancak, Seçenek C geçerli değil ! Bir senkronizasyon ilişkisi yalnızca aynı nesne üzerinde alma / bırakma işlemleri ile kurulabilir . Sizin durumunuzda tamamen farklı ve bağımsız iki nesneniz var dummy1ve dummy2. Ancak bunlar önce gerçekleşecek bir ilişki kurmak için kullanılamaz. Aslında, atomik değişkenler tamamen yerel olduğu için (yani, sadece bir iş parçacığına dokundukları için), derleyici bunları as-kuralına göre kaldırmakta serbesttir .

Güncelleme

Seçenek A:
Ben varsayalım set()ve check()bazı atomik değerin üzerinde işlem yoktur. Sonra aşağıdaki duruma sahibiz (-> önce dizilenmiş anlamına gelir ):

  • set()-> fence1(seq_cst)->y.load()
  • y.store(true)-> fence2(seq_cst)->check()

Böylece aşağıdaki kuralı uygulayabiliriz:

Atomik işlemler için bir ve B bir atom üzerinde nesnenin M , bir değiştirir M ve oda varsa, değerini alır memory_order_seq_cstçitler X ve Y, bu tür bir önceki sekanslanır X , Y, daha önce sıralanmış olan B ve X, önce gelen Y bölgesindeki S , daha sonra B, ya A'nın etkilerini ya da daha sonra M'nin modifikasyon sırasındaki bir modifikasyonunu gözlemler .

Yani ya check()depolanan değeri görür ya setda y.load()yazılan değeri görür y.store()(üzerindeki yişlemler bile kullanabilir memory_order_relaxed).

Seçenek C: C ++ 17 standart durumları [32.4.3, Güç P1347]:

Etkilenen tüm yerler için "önce gerçekleşir" siparişi ve değişiklik siparişleriyle tutarlı olarak tüm operasyonlarda tek bir toplam sipariş S olacaktır memory_order_seq_cst[...]

Buradaki önemli kelime "tutarlı" dır. Bu bir operasyon eğer ima A olur daha önce bir operasyon B , daha sonra bir önceki zorunlu B de S . Sadece bazı operasyon nedeniyle: biz ters anlaması böylece Ancak, mantıksal ima tek yönlü-cadde olan C önündeki bir operasyon D de S anlamına gelmez C önce olur D .

Özellikle, iki ayrı nesne üzerindeki iki seq-cst işlemi, ilişkiler S'de tamamen sipariş edilmiş olsa bile, ilişkiden önce bir olay oluşturmak için kullanılamaz . Ayrı nesneler üzerinde işlem sipariş etmek istiyorsanız, seq-cst'ye başvurmanız gerekir. - çitler (bkz. Seçenek A).


Seçenek C'nin geçersiz olduğu açık değildir. özel nesnelerde bile seq-cst işlemleri yine de bir dereceye kadar başka işlemler sipariş edebilir. Üzerinde senkronizasyon olmadığı konusunda anlaştık, ancak hangisinin ya da çubukun çalıştığını (ya da görünüşte hiçbirini) umursamıyoruz, sadece ikisi de çalışmıyor. Sıralı-öncesi ilişki ve seq-cst işlemlerinin toplam sırası (var olması gerekir) bize bunu vereceğini düşünüyorum.
Peter Cordes

Teşekkürler @mpoeter. Lütfen A Seçeneği hakkında bilgi verebilir misiniz? Cevabınızdaki üç mermiden hangisi burada geçerlidir? IIUC eğer y.load()etkisini görmüyor y.store(1), o zaman S, o kurallarından kanıtlayabilirim atomic_thread_fencethread_a ait öncedir atomic_thread_fencethread_b arasında. Görmediğim, bundan set()yan etkilerin görülebileceği sonucuna nasıl varılacağıdır check().
qbolec

1
@qbolec: Cevabımı A seçeneği hakkında daha fazla ayrıntıyla güncelledim
mpoeter

1
Evet, yerel bir seq-cst işlemi yine de tüm seq-cst işlemlerindeki tek toplam sipariş S'nin bir parçası olacaktır . Ancak S , "sadece" önceki sipariş ve değişiklik siparişleriyle tutarlıdır , yani A , B'den önce olursa , A , S'den önce B'den önce gelmelidir . Ama ters sırf yani garanti edilmez bir ilerlettiği B içinde S , biz değil çıkarabiliriz , o bir olur daha önce B .
mpoeter

1
Peki, paralel olarak setve checkgüvenli bir şekilde yürütülebildiğini varsayarak, muhtemelen bu, performans kritik ise, paylaşılan değişken üzerinde çekişmeyi önlediğinden, muhtemelen Seçenek A ile giderim y.
mpoeter

1

İlk örnekte, y.load()0 okumak daha y.load()önce olduğu anlamına gelmez y.store(1).

Bununla birlikte, bir seq_cst yükünün ya toplam sıradaki son seq_cst deposunun değerini ya da daha önce gerçekleşmeyen bazı seq_cst olmayan mağazanın değerini döndürmesi kuralı sayesinde daha önce tek toplam sırada olduğunu ima eder. (bu durumda mevcut değildir). Yani toplam siparişten y.store(1)daha erken olsaydı, 1 geri dönecekti.y.load()y.load()

Tek toplam siparişin bir döngüsü olmadığı için kanıt hala doğrudur.

Bu çözüme ne dersiniz?

std::atomic<int> x2{0},y{0};

void thread_a(){
  set();
  x2.store(1);
  if(!y.load()) foo();
}

void thread_b(){
  y.store(1);
  if(!x2.load()) bar();
}

OP sorunu "X" üzerinde hiçbir kontrole sahip değilim - sarıcı makrolar ya da bir şey arkasında ve seq-cst mağaza / yük olmayabilir. Soruyu daha iyi vurgulamak için güncelledim.
Peter Cordes

@PeterCordes Fikir üzerinde kontrol sahibi olduğu başka bir "x" oluşturmaktı. Daha net hale getirmek için cevabımda "x2" olarak yeniden adlandıracağım. Eminim bazı gereksinimi kaçırıyorum, ama tek gereklilik foo () ve bar () her ikisi de çağrılmadığından emin olmak ise, o zaman bu tatmin eder.
Tomek Czajka

Öyleyse if(false) foo();ama OP'nin de istemediğini düşünüyorum: P İlginç nokta ama OP'nin koşullu çağrıların belirledikleri koşullara dayalı olmasını istediğini düşünüyorum!
Peter Cordes

1
Merhaba @ TomekCzajka, yeni çözüm önermek için zaman ayırdığınız için teşekkür ederiz. Benim özel durumumda işe yaramaz, çünkü önemli yan etkilerini atlar check()(gerçek dünya anlamı için soruma yaptığım yoruma bakın set,check,foo,bar). Bunun if(!x2.load()){ if(check())x2.store(0); else bar(); }yerine çalışabileceğini düşünüyorum .
qbolec

1

@mpoeter, Seçenek A ve B'nin neden güvenli olduğunu açıkladı.

Gerçek uygulamalarda pratikte, Seçenek A'nın sadece std::atomic_thread_fence(std::memory_order_seq_cst)Konu A'da değil, B'de olması gerektiğini düşünüyorum .

Uygulamada seq-cst depoları tam bir bellek bariyeri içerir veya AArch64'te en azından daha sonra edinme veya seq_cst yükleri ile yeniden sıralama yapamaz ( stlrsıralı bırakma, ldarönbellekten okunmadan önce depo arabelleğinden boşalmalıdır ).

C + -> asm eşlemeleri , atomik depolar veya atomik yükler üzerindeki depo tamponunu boşaltma maliyetini koyma seçeneğine sahiptir. Gerçek uygulamalar için akıllıca bir seçim atomik yükleri ucuz hale getirmektir, bu nedenle seq_cst mağazaları tam bir bariyer içerir (StoreLoad dahil). Seq_cst yükleri çoğu yük almakla aynıdır.

(Ancak GÜÇ değil; yükler bile ağır çekirdek senkronizasyonuna ihtiyaç duyar = aynı çekirdek üzerindeki diğer SMT iş parçacıklarından mağaza yönlendirmeyi durdurmak için IRIW yeniden sıralamasına neden olabilecek tam bariyer gerekir, çünkü seq_cst tüm iş parçacıklarının sırasını kabul etmesini gerektirir Farklı dizilerdeki farklı konumlara iki atom yazımı her zaman diğer diziler tarafından aynı sırada mı görünecek ? )

(Elbette resmi bir güvenlik garantisi için , edinme / bırakma setini () -> kontrol etmeyi) bir seq_cst senkronizasyonu haline getirmek için her ikisinde de bir çite ihtiyacımız var. rahat kontrol diğer iş parçacıklarının POV çubuğu ile yeniden sipariş verebilir.)


Seçenek C ile ilgili asıl sorun, senkronize olabilecek bazı varsayımsal gözlemcilere yve kukla işlemlere bağlı olmasıdır. Ve böylece, derleyicinin bariyer tabanlı bir ISA için karar verirken bu düzeni korumasını bekleriz.

Bu gerçek ISA'larda pratikte geçerli olacak; her iki iş parçacığı da tam bir bariyer veya eşdeğeri içerir ve derleyiciler atomları optimize etmez (henüz). Ancak elbette "bariyer tabanlı bir ISA'ya derlemek" ISO C ++ standardının bir parçası değildir. Tutarlı paylaşılan önbellek, asm mantığı için var olan ancak ISO C ++ mantığı için var olmayan varsayımsal gözlemcidir.

Seçenek C'nin çalışması için, bazı ISO C ++ kurallarını ihlal etmek üzere dummy1.store(13);/ y.load()/ set();(Konu B'de görüldüğü gibi) gibi bir sıralamaya ihtiyacımız var .

Bu ifadeleri çalıştıran iş parçacığı, ilk önce yürütülmüş gibi davranmalıdır set()(Önceki Sıralı nedeniyle). Bu iyi, çalışma zamanı bellek sıralaması ve / veya işlemlerin derleme zamanı yeniden sıralama hala bunu yapabilir.

İki seq_cst ops d1=13ve yÖnceden Sıralı (program sırası) ile tutarlıdır. set()seq_cst ops için var olması gereken global düzene katılmaz çünkü seq_cst değildir.

B iş parçacığı dummy1.store ile eşitlenmez, bu nedenlesetd1=13 bu atama bir serbest bırakma işlemi olsa da, göreli olarak uygulanmadan önce gerçekleşmesi gerekmez .

Başka olası kural ihlalleri görmüyorum; Burada setSıralı-Önceki ile tutarlı olması gereken hiçbir şey bulamıyorum d1=13.

"Dummy1.store release set ()" mantığı kusurdur. Bu sıralama yalnızca kendisiyle senkronize edilen veya gözlemleyen gerçek bir gözlemci için geçerlidir. @Mpoeter'ın yanıtladığı gibi, seq_cst toplam düzeninin varlığı ilişkilerden önce oluşmaz ya da ima etmez ve seq_cst dışında sipariş vermeyi resmi olarak garanti eden tek şey budur.

Bu yeniden sıralamanın gerçekten çalışma zamanında gerçekleşebileceği tutarlı paylaşılan önbelleğe sahip her türlü "normal" CPU mantıklı görünmüyor. (Ama eğer bir derleyici kaldırabilseydi dummy1ve dummy2sonra açıkça bir sorunumuz olurdu ve bence bu standart tarafından izin verildi.)

Ancak C ++ bellek modeli, bir depolama arabelleği, paylaşılan tutarlı önbellek veya izin verilen yeniden sıralamanın turnusol testleri açısından tanımlanmadığından, akıl sağlığının gerektirdiği şeyler C ++ kuralları tarafından resmen gerekli değildir. Bu belki de iş parçacığı özel olduğu ortaya çıkan seq_cst değişkenlerinin bile optimize edilmesine izin vermek amaçlıdır. (Şimdiki derleyiciler bunu veya atomik nesnelerin herhangi bir optimizasyonunu yapmazlar.)

Bir iş parçacığının gerçekten set()son görebildiği bir uygulama, diğeri set()ilk sesleri akıl almaz görebiliyordu . POWER bile bunu yapamazdı; hem seq_cst yükü hem de deposu POWER için tam engelleri içerir. (IRIW yeniden sıralamasının burada alakalı olabileceğini yorumlarda belirtmiştim; C ++ 'ın acq / rel kuralları bunu karşılayacak kadar zayıftır, ancak senkronizasyon ile veya diğer önce gerçekleşen durumlar dışında toplam garanti eksikliği herhangi bir HW'den çok daha zayıftır. )

Aslında orada sürece C ++ olmayan seq_cst için her şeyi garanti etmez ise sadece o gözlemci için daha sonra bir gözlemci ve. Biri olmadan Schroedinger'in kedi bölgesindeyiz. Ya da, eğer ormana iki ağaç düşerse, biri diğerinin önüne mi düştü? (Büyük bir ormansa, genel görelilik gözlemciye bağlı olduğunu ve evrensel bir eşzamanlılık kavramının olmadığını söyler.)


@mpoeter, bir derleyicinin seq_cst nesnelerinde bile kukla yük ve depolama işlemlerini kaldırabileceğini önerdi.

Hiçbir şeyin bir işlemle senkronize olamayacağını kanıtlayabildiklerinde bunun doğru olabileceğini düşünüyorum. örneğin dummy2, işlevden kaçamayan bir derleyici muhtemelen seq_cst yükünü kaldırabilir.

Bunun en az bir gerçek dünya sonucu vardır: AArch64 için derleme yaparsanız, daha önceki bir seq_cst mağazasının pratikte daha sonra rahat işlemlerle yeniden sıralanmasına izin verir, bu da seq_cst deposu + daha önce depo arabelleğini boşaltan yük ile mümkün olmazdı sonraki yükler çalıştırılabilir.

Şüphesiz, ISO C ++ bunu yasaklamasa da, mevcut derleyiciler atomları hiç optimize etmez; bu standartlar komitesi için çözülmemiş bir sorundur .

C ++ bellek modeli örtülü bir gözlemci veya tüm iş parçacığı sipariş üzerinde kabul bir gereksinimi olmadığı için bu bence izin verilir. Tutarlı önbelleklere dayalı bazı garantiler sağlar, ancak tüm iş parçacıklarının eşzamanlı olması için görünürlük gerektirmez.


Güzel özet! Pratikte muhtemelen sadece A ipliğinin bir seq-cst çitine sahip olmasının yeterli olacağını kabul ediyorum . Ancak, C ++ standardına dayalı olmaz biz en son değeri görmelerini gerekli garantisi var set()hala yanı iplik B'de çit kullanırsınız yüzden. Bir seq-cst çit ile rahat bir mağaza zaten bir seq-cst mağaza ile aynı kodu üreteceğini varsayalım.
mpoeter

@mpoeter: evet, resmi olarak değil, sadece pratikte konuşuyordum. Bu bölümün sonuna bir not eklendi. Ve evet, çoğu ISA üzerinde pratikte bir seq_cst mağazasının genellikle sadece düz mağaza (rahat) + bir bariyer olduğunu düşünüyorum. Ya da değil; GÜÇ üzerinde bir seq-cst mağazası mağazadan sync önce (ağır ağırlık) yapar , sonra hiçbir şey yapmaz. godbolt.org/z/mAr72P Fakat seq-cst yüklerinin her iki tarafta da bazı engellere ihtiyacı var.
Peter Cordes

1

ISO standardında std :: mutex, seq_cst değil, yalnızca sipariş alma ve bırakma garantisine sahiptir.

Ancak hiçbir seq_cstişlemin özelliği olmadığı gibi hiçbir şeyin "seq_cst siparişi" vereceği garanti edilmez.

seq_cststd::atomicalternatif bir atomik sınıfın belirli bir uygulamasının tüm operasyonları için bir garantidir . Bu nedenle, sorunuz doğru değil.

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.