C ++ 11 standart bir bellek modeli tanıttı. Bunun anlamı ne? Peki C ++ programlamasını nasıl etkileyecek?


1894

C ++ 11 standart bir bellek modeli tanıttı, ama bu tam olarak ne anlama geliyor? Peki C ++ programlamasını nasıl etkileyecek?

Bu makale ( Herb Sutter'dan alıntı yapan Gavin Clarke tarafından ),

Bellek modeli, C ++ kodunun derleyiciyi kimin yaptığını ve hangi platformda çalıştığından bağımsız olarak çağrılacak standartlaştırılmış bir kütüphaneye sahip olduğu anlamına gelir. Farklı iş parçacıklarının işlemcinin belleğiyle nasıl konuşacağını kontrol etmenin standart bir yolu vardır.

Sutter , "[kodu] standartta bulunan farklı çekirdekler arasında bölmekten bahsederken, bellek modelinden bahsediyoruz. İnsanların kodda yapacakları aşağıdaki varsayımları bozmadan optimize edeceğiz." Dedi.

Eh, olabilir ezberlemek (Ben doğum beri kendi hafıza modeli yaşadım: P) bu ve benzeri paragraflar mevcut çevrimiçi ve hatta sorulara cevap olarak göndermek başkaları tarafından sorulan, ama dürüst olmak gerekirse, ben tam olarak anlamıyorum olabilir bu.

C ++ programcıları daha önce bile çok iş parçacıklı uygulamalar geliştirmek için kullanıldılar, bu yüzden POSIX iş parçacığı veya Windows iş parçacığı veya C ++ 11 iş parçacığı olması ne kadar önemli? Avantajları nelerdir? Düşük seviyeli detayları anlamak istiyorum.

Ayrıca, C ++ 11 bellek modelinin bir şekilde C ++ 11 çoklu iş parçacığı desteğiyle ilişkili olduğu hissini alıyorum, çünkü bu ikisini birlikte sık sık görüyorum. Öyleyse, tam olarak nasıl? Neden ilişkili olmalılar?

Çok iş parçacığının iç kısmının nasıl çalıştığını ve genel olarak bellek modelinin ne anlama geldiğini bilmediğim için lütfen bu kavramları anlamama yardımcı olun. :-)


3
@curiousguy: Ayrıntılı ...
Nawaz

4
@curiousguy: O zaman bir blog yazın ve bir düzeltme önerin. Noktanızı geçerli ve mantıklı hale getirmenin başka bir yolu yoktur.
Nawaz

2
Bu siteyi Q sormak ve fikir alışverişinde bulunmak için bir yer olarak yanlış anladım. Benim hatam; o, atma özellikleri konusunda açıkça çelişkili olsa bile Herb Sutter ile aynı fikirde olamayacağınız uygunluk yeri.
curiousguy

5
@curiousguy: C ++, Standard'ın söylediği şeydir, internetteki rastgele bir adamın söylediği şey değildir. Yani evet, Standarda uygunluk olmalı . C ++, Standarda uymayan herhangi bir şey hakkında konuşabileceğiniz açık bir felsefe DEĞİLDİR .
Nawaz

3
"Hiçbir C ++ programının iyi tanımlanmış davranışlara sahip olamayacağını kanıtladım." . Herhangi bir kanıt olmadan uzun iddialar!
Nawaz

Yanıtlar:


2205

İlk olarak, bir Dil Avukatı gibi düşünmeyi öğrenmelisiniz.

C ++ belirtimi herhangi bir derleyici, işletim sistemi veya CPU ile ilgili değildir. Gerçek sistemlerin genelleştirilmesi olan soyut bir makineye atıfta bulunur . Dil Avukatı dünyasında, programcının işi soyut makine için kod yazmaktır; derleyicinin işi bu kodu bir beton makinesinde gerçekleştirmektir. Şartnameye katı bir şekilde kodlama yaparak, kodunuzun bugün veya 50 yıl sonra uyumlu bir C ++ derleyicisine sahip herhangi bir sistemde değişiklik yapmadan derlenip çalışacağından emin olabilirsiniz.

C ++ 98 / C ++ 03 spesifikasyonundaki soyut makine temelde tek iş parçacıklıdır. Bu nedenle, spesifikasyona göre "tamamen taşınabilir" olan çok iş parçacıklı C ++ kodunu yazmak mümkün değildir. Spesifikasyon, bellek yüklerinin ve depolarının atomisitesi veya yüklerin ve depoların meydana gelme sırası hakkında hiçbir şey söylemez , muteksler gibi şeylere aldırmayın.

Elbette, pthreads veya Windows gibi belirli beton sistemleri için pratikte çok iş parçacıklı kod yazabilirsiniz. Ancak C ++ 98 / C ++ 03 için çok iş parçacıklı kod yazmanın standart bir yolu yoktur .

C ++ 11'deki soyut makine tasarım açısından çok iş parçacıklı. Aynı zamanda iyi tanımlanmış bir bellek modeline sahiptir ; yani derleyicinin belleğe erişim söz konusu olduğunda neler yapabileceğini ve yapamayacağını söyler.

Bir çift global değişkenin iki iş parçacığıyla aynı anda erişildiği aşağıdaki örneği düşünün:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

Thread 2 çıktısı ne olabilir?

C ++ 98 / C ++ 03 altında, bu Tanımlanmamış Davranış bile değildir; sorunun kendisi anlamsızdır, çünkü standart "iş parçacığı" olarak adlandırılan herhangi bir şeyi düşünmez.

C ++ 11 altında, sonuç Tanımlanmamış Davranıştır, çünkü yüklerin ve depoların genel olarak atomik olması gerekmez. Bu bir gelişme gibi görünmeyebilir ... Ve kendi başına, öyle değil.

Ancak C ++ 11 ile şunu yazabilirsiniz:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

Şimdi işler çok daha ilginç hale geliyor. Her şeyden önce, buradaki davranış tanımlanır . İş parçacığı 2 artık 0 0(İş parçacığı 1'den önce çalışıyorsa), 37 17(İş parçacığı 1'den sonra çalışıyorsa) veya 0 17(İş parçacığı 1 x'e atadıktan sonra ancak y'ye atamadan önce çalışıyorsa ) yazdırabilir .

Ya baskı yapılamamasıdır 37 011 C ++ atomik yükler / mağazalar için varsayılan mod zorlamak için, çünkü sıralı tutarlılık . Bu, tüm yüklerin ve depoların her iş parçacığına yazdığınız sırayla "sanki" olması gerektiği anlamına gelirken, iş parçacıkları arasındaki işlemler sistemden istendiği gibi araya eklenebilir. Yani atomics varsayılan davranışı hem de sağlar atomicity ve sipariş yükler ve mağazalar için.

Şimdi, modern bir CPU'da, sıralı tutarlılığın sağlanması pahalı olabilir. Özellikle, derleyicinin buradaki her erişim arasında tam gelişmiş bellek engelleri yayması muhtemeldir. Ancak algoritmanız sıra dışı yükleri ve depoları tolere edebilirse; örneğin, atomisite gerektiriyor ancak sipariş vermiyorsa; yani, 37 0bu programdan çıktı olarak tolere edilebilirse, bunu yazabilirsiniz:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

CPU ne kadar modern olursa, önceki örnekten daha hızlı olma olasılığı o kadar yüksektir.

Son olarak, sadece belirli yükleri ve depoları düzenli tutmanız gerekiyorsa, şunları yazabilirsiniz:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

Bu bizi sipariş edilen yüklere ve depolara geri götürüyor - bu yüzden 37 0artık olası bir çıktı değil - ancak bunu minimum ek yük ile yapıyor. (Bu önemsiz örnekte, sonuç tam gelişmiş sıralı tutarlılıkla aynıdır; daha büyük bir programda böyle olmaz.)

Tabii ki, görmek istediğiniz tek çıktılar 0 0ya da ise 37 17, bir muteksi orijinal kodun etrafına sarabilirsiniz. Ama şimdiye kadar okuduysanız, bunun nasıl çalıştığını zaten biliyorsunuzdur ve bu cevap zaten istediğimden daha uzundur :-).

Yani, sonuçta. Muteksler harika ve C ++ 11 bunları standartlaştırıyor. Ancak bazen performans nedenlerinden dolayı daha düşük seviyeli ilkeller (örn. Klasik çift ​​kontrollu kilitleme deseni ) istersiniz . Yeni standart, muteksler ve koşul değişkenleri gibi üst düzey gadget'lar sağlar ve ayrıca atomik tipler ve bellek bariyerinin çeşitli lezzetleri gibi düşük seviye gadget'lar sağlar. Artık tamamen standart tarafından belirtilen dilde karmaşık, yüksek performanslı eşzamanlı rutinler yazabilirsiniz ve kodunuzun hem günümüz sistemlerinde hem de yarınlarda derlenip değişmeyeceğinden emin olabilirsiniz.

Açıkçası, bir uzman değilseniz ve bazı ciddi düşük seviyeli kodlar üzerinde çalışmadığınız sürece, mutekslere ve koşul değişkenlerine bağlı kalmalısınız. Yapmayı planladığım şey bu.

Bu konu hakkında daha fazla bilgi için bu blog yayınına bakın .


37
Güzel cevap, ama bu gerçekten yeni ilkel bazı gerçek örnekler için yalvarıyor. Ayrıca, ilkelleri olmadan bellek sıralaması ön C ++ 0x ile aynı olduğunu düşünüyorum: hiçbir garanti yoktur.
John Ripley

5
@John: Biliyorum, ama hala ilkelleri kendim öğreniyorum :-). Ayrıca onlar bayt erişim (sipariş olmasa da) atomik olduğunu garanti düşünüyorum bu yüzden benim örnek için "char" ile gitti ... Ama ben bu konuda% 100 emin değilim ... Herhangi bir iyi önermek istiyorsanız " öğretici "referansları
Nemo

48
@Nawaz: Evet! Bellek erişimi derleyici veya CPU tarafından yeniden sıralanabilir. Önbellekleri ve spekülatif yükleri düşünün (örn.). Sistem belleğinin vurulma sırası, kodladığınız gibi bir şey olamaz. Derleyici ve CPU, bu tür siparişlerin tek iş parçacıklı kodu kırmamasını sağlayacaktır . Çok iş parçacıklı kod için "bellek modeli" olası yeniden sıralamaları ve iki iş parçacığı aynı anda aynı konumu okuyor / yazıyorsa ve her ikisini de nasıl kontrol edeceğinizi gösterir. Tek iş parçacıklı kod için bellek modeli önemsizdir.
Nemo

26
@Nawaz, @Nemo - Küçük bir ayrıntı: Yeni bellek modeli, gibi belirli ifadelerin tanımsızlığını belirlediği sürece tek iş parçacıklı kodla ilgilidir i = i++. Eski dizi noktaları kavramı atılmıştır; Yeni standart belirtir bir kullanarak aynı şey sıralandı-evvel daha genel arası dişin sadece özel bir durumudur ilişki olur-öncesi kavram.
JohannesD

17
@ AJG85: Taslak C ++ 0x spec bölüm 3.6.2, "Statik depolama süresi (3.7.1) veya iş parçacığı depolama süresi (3.7.2) olan değişkenler, başka bir başlatma yapılmadan önce sıfır başlatılacaktır (8.5) yerleştirin." Bu örnekte x, y global olduğu için statik depolama süresine sahip olduklarından sıfır başlatılacaklarına inanıyorum.
Nemo

345

Sadece bellek tutarlılık modellerini (veya kısaca bellek modellerini) anladığım benzetmeyi vereceğim. Esinlenerek Leslie Lamport'un "Zaman, Saatler ve Dağıtılmış Bir Sistemdeki Olayların Sıralanması" adlı seminal makalesinden esinlenilmiştir . Analoji uygundur ve temel bir öneme sahiptir, ancak birçok insan için aşırı olabilir. Ancak, umarım bellek tutarlılık modelleri hakkında akıl yürütmeyi kolaylaştıran zihinsel bir görüntü (resimsel bir temsil) sağlar.

Tüm bellek konumlarının geçmişini, yatay eksenin adres alanını temsil ettiği bir boşluk-zaman diyagramında görüntüleyelim (yani, her bellek konumu bu eksen üzerindeki bir nokta ile temsil edilir) ve dikey eksen zamanı temsil eder (göreceğiz, genel olarak, evrensel bir zaman kavramı yoktur). Bu nedenle, her bellek konumu tarafından tutulan değerlerin geçmişi, bu bellek adresindeki dikey bir sütunla temsil edilir. Her değer değişikliğinin nedeni, bu konuma yeni bir değer yazan evrelerden biridir. Bir bellek görüntüsü ile , belirli bir zamanda belirli bir iş parçacığı tarafından gözlemlenebilen tüm bellek konumlarının değerlerinin toplamını / kombinasyonunu kastedeceğiz .

"Bellek Tutarlılığı ve Önbellek Tutarlılığı Üzerine Bir Astar" dan alıntı

Sezgisel (ve en kısıtlayıcı) bellek modeli, çok iş parçacıklı bir yürütmenin, her bir kurucu iş parçacığının sıralı yürütmelerinin serpiştirilmesi gibi görünmesi gereken sıralı tutarlılıktır (SC), dişler tek çekirdekli bir işlemcide zamanla çoğaltılmıştır.

Bu küresel bellek sırası programın bir işleminden diğerine değişebilir ve önceden bilinmeyebilir. SC'nin karakteristik özelliği, eşzamanlılık düzlemlerini (yani bellek görüntüleri) temsil eden adres-uzay-zaman diyagramındaki yatay dilimler kümesidir . Belirli bir düzlemde, tüm olayları (veya bellek değerleri) eşzamanlıdır. Tüm evrelerin hangi bellek değerlerinin eşzamanlı olduğu konusunda hemfikir olduğu Mutlak Zaman kavramı vardır. SC'de her an, tüm evreler tarafından paylaşılan yalnızca bir bellek görüntüsü vardır. Bu, her an, tüm işlemciler bellek görüntüsü (yani, belleğin toplam içeriği) üzerinde hemfikirdir. Bu, tüm iş parçacıklarının tüm bellek konumları için aynı değer sırasını görüntülediği anlamına gelmez, aynı zamanda tüm işlemcilerin aynı şeyi gözlemlediğini gösterir.tüm değişkenlerin değer kombinasyonları . Bu, tüm bellek işlemlerinin (tüm bellek konumlarında) tüm iş parçacıkları tarafından aynı sırayla izlendiğini söylemekle aynıdır.

Rahat bellek modellerinde, her iş parçacığı adres-alan-zamanını kendi yoluna göre keser, tek kısıtlama her iş parçacığının dilimlerinin birbirini geçmemesidir, çünkü tüm iş parçacıkları her bir ayrı bellek konumunun tarihini kabul etmelidir (elbette , farklı ipliklerin dilimleri birbirini kesebilir ve keser). Dilimlemenin evrensel bir yolu yoktur (adres-uzay-zamanın ayrıcalıklı bir şekilde yapraklanması yoktur). Dilimlerin düzlemsel (veya doğrusal) olması gerekmez. Kavisli olabilirler ve başka bir evre tarafından yazıldıkları sıradan başka bir evre tarafından yazılan değerleri okuyabilen şey budur. Farklı bellek konumlarının tarihleri, belirli bir evre tarafından görüntülendiğinde birbirine göre keyfi olarak kayabilir (veya gerilebilir). Her iş parçacığı hangi olayların (veya eşdeğer olarak bellek değerlerinin) eşzamanlı olduğu konusunda farklı bir anlayışa sahip olacaktır. Bir iş parçacığına eşzamanlı olan olaylar (veya bellek değerleri) başka bir iş parçacığına eşzamanlı değildir. Böylece, rahat bir bellek modelinde, tüm evreler hala her bir bellek konumu için aynı geçmişi (yani değerler dizisini) gözlemler. Ancak farklı bellek görüntülerini gözlemleyebilirler (diğer bir deyişle, tüm bellek konumlarının değer kombinasyonları). Aynı iş parçacığı tarafından sırayla iki farklı bellek konumu yazılsa bile, yeni yazılan iki değer diğer iş parçacıkları tarafından farklı sırayla gözlenebilir.

[Wikipedia'dan resim] Wikipedia'dan resim

Einstein'ın Özel Görelilik Teorisi'ne aşina olan okuyucular , neyi kastettiğimi fark edecekler. Minkowski'nin sözlerini bellek modelleri alanına çevirmek: adres alanı ve zaman, adres-uzay-zamanın gölgeleridir. Bu durumda, her gözlemci (yani, iş parçacığı) olayların gölgelerini (yani bellek depoları / yükleri) kendi dünya çizgisine (yani zaman eksenine) ve kendi eşzamanlılık düzlemine (adres-uzay ekseni) yansıtacaktır. . C ++ 11 bellek modelindeki konular, özel görelilikte birbirlerine göre hareket eden gözlemcilere karşılık gelir . Sıralı tutarlılık Galilean uzay-zamanına karşılık gelir (yani, tüm gözlemciler mutlak bir olay sırası ve küresel bir eşzamanlılık duygusu üzerinde anlaşırlar).

Bellek modelleri ve özel görelilik arasındaki benzerlik, her ikisinin de genellikle nedensel küme adı verilen kısmen sıralı bir olay kümesi tanımlamasından kaynaklanmaktadır. Bazı olaylar (yani bellek depoları) diğer olayları etkileyebilir (ancak bundan etkilenmez). Bir C ++ 11 iş parçacığı (veya fizikteki gözlemci), bir olaylar zincirinden (yani tamamen sıralı bir kümeden) başka bir şey değildir (örneğin, bellek yükler ve muhtemelen farklı adreslere depolar).

İzafiyette, bazı düzenler kısmen düzenlenmiş olayların görünüşte kaotik resmine geri yüklenir, çünkü tüm gözlemcilerin üzerinde anlaştığı tek zamansal emir, “zamana benzer” olaylar (yani prensip olarak herhangi bir parçacık tarafından bağlanabilir olan olaylar yavaşlar) vakumdaki ışık hızından daha fazla). Sadece zamana bağlı olaylar değişmez şekilde sıralanır. Fizikte Zaman, Craig Callender .

C ++ 11 bellek modelinde, bu yerel nedensellik ilişkilerini oluşturmak için benzer bir mekanizma (edinme-bırakma tutarlılık modeli) kullanılır .

Bellek tutarlılığının bir tanımını ve SC'yi terk etme motivasyonunu sağlamak için, "Bellek Tutarlılığı ve Önbellek Tutarlılığı Üzerine Bir Astar" dan alıntı yapacağım

Paylaşılan bir bellek makinesi için, bellek tutarlılık modeli, bellek sisteminin mimari olarak görülebilir davranışını tanımlar. Tek bir işlemci çekirdeği için doğruluk kriteri davranışı “ tek bir doğru sonuç ” ile “ birçok yanlış alternatif ” arasında bölüştürür . Bunun nedeni, işlemcinin mimarisinin bir iş parçacığının yürütülmesinin, belirli bir giriş durumunu, sıra dışı bir çekirdekte bile, iyi tanımlanmış tek bir çıkış durumuna dönüştürmesini zorunlu kılmasıdır. Bununla birlikte, paylaşılan bellek tutarlılık modelleri, birden çok iş parçacığının yükleri ve depolarıyla ilgilidir ve genellikle birçok doğru yürütmeye izin verirbirçok (daha fazla) yanlış olana izin vermemek. Birden fazla doğru yürütme olasılığı, ISA'nın birden çok iş parçacığının aynı anda yürütülmesine izin vermesinden kaynaklanır, çoğu zaman farklı iş parçacıklarından gelen talimatların birçok olası yasal araya girmesi ile.

Gevşemiş veya zayıf bellek tutarlılığı modelleri, güçlü modellerde çoğu bellek düzeninin gereksiz olması gerçeğiyle motive edilir. Bir iş parçacığı on veri öğesini ve ardından bir senkronizasyon bayrağını güncellerse, programcılar genellikle veri öğelerinin birbirine göre güncellenip güncellenmediğini umursamazlar, ancak tüm veri öğelerinin bayrak güncellenmeden önce güncellenmesini (genellikle ÇİT talimatları kullanılarak uygulanır) ). Rahat modeller, bu artan sipariş esnekliğini yakalamaya çalışır ve yalnızca programcıların gerektirdiği siparişleri korurSC'nin hem daha yüksek performansını hem de doğruluğunu elde etmek için. Örneğin, belirli mimarilerde, FIFO yazma tamponları, sonuçları önbelleklere yazmadan önce taahhütlü (emekli) mağazaların sonuçlarını tutmak için her çekirdek tarafından kullanılır. Bu optimizasyon performansı artırır ancak SC'yi ihlal eder. Yazma arabelleği, bir mağaza özlemine hizmet etme gecikmesini gizler. Mağazalar yaygın olduğu için, çoğunda durmaktan kaçınmak önemli bir faydadır. Tek çekirdekli bir işlemci için, bir veya daha fazla A deposu yazma arabelleğinde olsa bile, A adresine adreslenen bir yükün en son deponun değerini A'ya döndürmesini sağlayarak mimari olarak görünmez hale getirilebilir. Bu tipik olarak ya en son deponun değerini A'dan yüke atlayarak yapılır, burada “en yenisi” program sırasına göre belirlenir, veya yazma arabelleğinde A'ya bir mağaza varsa A yükünü durdurarak. Birden fazla çekirdek kullanıldığında, her birinin kendi baypas yazma tamponu olacaktır. Yazma arabellekleri olmadan donanım SC'dir, ancak yazma arabellekleri ile yazma arabelleklerini çok çekirdekli bir işlemcide mimari olarak görünür kılmaz.

Bir çekirdekte, mağazaların girildikleri sıradan farklı bir sırayla ayrılmalarına izin veren FIFO olmayan bir yazma arabelleği varsa, mağaza-mağaza yeniden sıralaması gerçekleşebilir. Bu durum, ikinci mağaza isabet ederken ilk mağaza önbellekte kaybolursa veya ikinci mağaza daha önceki bir mağaza ile birleşebiliyorsa (yani ilk mağazadan önce) oluşabilir. Yük yükünün yeniden sıralanması, talimatları program sırasından yürüten dinamik olarak zamanlanmış çekirdeklerde de olabilir. Bu, başka bir çekirdeğin mağazalarını yeniden sıralamakla aynı şekilde davranabilir (İki iş parçacığı arasında serpiştirme örneği oluşturabilir misiniz?). Daha önceki bir yükü daha sonraki bir depo ile yeniden sıralamak (bir yük deposu yeniden sıralaması), onu koruyan kilidi açtıktan sonra bir değer yüklemek gibi (depo kilit açma işlemiyse) birçok yanlış davranışa neden olabilir.

Önbellek tutarlılığı ve bellek tutarlılığı bazen karışık olduğundan, bu alıntıya sahip olmak da öğreticidir:

Tutarlılıktan farklı olarak, önbellek tutarlılığı ne yazılım tarafından görülebilir ne de zorunludur. Tutarlılık, paylaşılan bellek sisteminin önbelleklerini, tek çekirdekli bir sistemdeki önbellekler kadar işlevsel olarak görünmez hale getirmeye çalışır. Doğru tutarlılık, bir programcının yüklerin ve depoların sonuçlarını analiz ederek bir sistemin önbellek olup olmadığını belirleyememesini sağlar. Bunun nedeni, doğru tutarlılığın önbelleklerin hiçbir zaman yeni veya farklı işlevsel davranışları etkinleştirmemesini sağlamasıdır (programcılar yine de zamanlamayı kullanarak olası önbellek yapısını çıkarabilirler)bilgi). Önbellek tutarlılık protokollerinin temel amacı, her bellek konumu için tek yazarlı çok okuyuculu (SWMR) değişmezi korumaktır. Tutarlılık ve tutarlılık arasındaki önemli bir ayrım, tutarlılığın bellek başına konum bazında , oysa tutarlılığın tüm bellek konumlarına göre belirlenmesidir .

Zihinsel resmimize devam ederek, SWMR değişmezi, herhangi bir yerde en fazla bir parçacığın olması gerekliliğine karşılık gelir, ancak herhangi bir yerde sınırsız sayıda gözlemci olabilir.


52
Özel göreliliğe sahip benzetme için +1, aynı benzetmeyi kendim yapmaya çalışıyorum. Çok sık, belirli bir sırayla birbiri arasına serpiştirilmiş farklı iş parçacıklarındaki işlemleri olarak davranışı yorumlamaya çalışan dişli kodu araştıran programcılar görüyorum ve onlara, hayır, çok işlemcili sistemlerde farklı <s arasındaki eşzamanlılık kavramını söylemeliyim > referans çerçeveleri </s> konuları artık anlamsız. Özel görelilik ile karşılaştırmak, sorunun karmaşıklığına saygı göstermelerini sağlamanın iyi bir yoludur.
Pierre Lebeaupin

71
Öyleyse Evrenin çok çekirdekli olduğu sonucuna varmalı mısınız?
Peter K

6
@PeterK: Kesinlikle :) Ve bu zaman resminin fizikçi Brian Greene tarafından çok güzel bir görselleştirmesi: youtube.com/watch?v=4BjGWLJNPcA&t=22m12s Bu 22. dakikada "Zaman Yanılsaması [Tam Belgesel]" ve 12 saniye.
Ahmed Nassar

2
Sadece ben mi yoksa 1D bellek modelinden (yatay eksen) 2D bellek modeline (eşzamanlılık düzlemleri) geçiyor mu? Bunu biraz kafa karıştırıcı buluyorum ama belki de anadili olmayan biri olduğum için ... Hala çok ilginç bir okuma.
Elveda SE

Önemli bir kısmı unuttunuz: " yüklerin ve depoların sonuçlarını analiz ederek " ... kesin zamanlama bilgisi kullanmadan.
curiousguy

115

Bu şimdi çok yıllık bir soru, ancak çok popüler olmak, C ++ 11 bellek modelini öğrenmek için harika bir kaynaktan bahsetmeye değer. Bunu başka bir tam cevap vermek için konuşmasını özetlemenin bir anlamı yok, ama bu aslında standardı yazan adam göz önüne alındığında, bence konuşmayı izlemeye değer.

Herb Sutter, Channel9 sitesinde, bölüm 1 ve bölüm 2'de bulunan "atomik <> Silahlar" başlıklı C ++ 11 bellek modeli hakkında üç saatlik bir konuşma yaptı . Konuşma oldukça teknik ve aşağıdaki konuları içeriyor:

  1. Optimizasyonlar, Yarışlar ve Bellek Modeli
  2. Sipariş - Ne: Edinme ve Bırakma
  3. Sıralama - Nasıl: Muteksler, Atomlar ve / veya Çitler
  4. Derleyiciler ve Donanımdaki Diğer Kısıtlamalar
  5. Kod Üretimi ve Performansı: x86 / x64, IA64, POWER, ARM
  6. Rahat Atomlar

Konuşma, API üzerinde değil, mantık, arka plan, kaputun altında ve sahne arkasıyla ilgili ayrıntılara giriyor (yalnızca POWER ve ARM senkronize yükü verimli bir şekilde desteklemediği için rahat semantiklerin standarda eklendiğini biliyor muydunuz?).


10
Bu konuşma gerçekten harika, tamamen izlemeye harcayacağınız 3 saat değerinde.
ZunTzu

5
@ZunTzu: Çoğu video oynatıcıda hızı orijinalinden 1,25, 1,5 hatta 2 katına ayarlayabilirsiniz.
Christian Severin

4
@eran slaytlara sahip misiniz? kanaldaki 9 tartışma sayfasındaki bağlantılar çalışmıyor.
athos

2
@athos Bende yok üzgünüm. Kanal 9 ile iletişime geçmeye çalışın, kaldırmanın kasıtlı olduğunu düşünmüyorum (tahminim Herb Sutter'den bağlantıyı aldıkları, olduğu gibi yayınladıkları ve daha sonra dosyaları kaldırdığı; ancak bu sadece bir spekülasyon ...).
eran

75

Bu, standardın artık çoklu iş parçacığı tanımladığı ve birden çok iş parçacığı bağlamında neler olduğunu tanımladığı anlamına gelir. Tabii ki, insanlar çeşitli uygulamalar kullandı, ancak bu neden hepimiz std::stringev haddelenmiş bir stringsınıf kullanabileceğimizi sormak gibi bir şey .

POSIX iş parçacıkları veya Windows iş parçacıkları hakkında konuşurken, aynı anda çalıştırmak için bir donanım işlevi olduğu için bu aslında x86 iş parçacıklarından bahsettiğiniz için bu bir yanılsama. C ++ 0x bellek modeli, ister x86, ister ARM veya MIPS'de olun , ya da başka bir şeyle karşılaşabileceğinizi garanti eder .


28
Posix iş parçacıkları x86 ile sınırlı değildir. Aslında, uygulandıkları ilk sistemler muhtemelen x86 sistemleri değildi. Posix iş parçacıkları sistemden bağımsızdır ve tüm Posix platformlarında geçerlidir. Aynı zamanda bir donanım özelliği olduğu da doğru değil çünkü Posix iş parçacıkları da kooperatif çoklu görev yoluyla da uygulanabilir. Ancak elbette çoğu iş parçacığı sorunu yalnızca donanım iş parçacığı uygulamalarında (ve hatta bazıları yalnızca çok işlemcili / çok çekirdekli sistemlerde) görülür.
celtschk

57

Bir bellek modeli belirtmeyen diller için , işlemci mimarisi tarafından belirtilen dil ve bellek modeli için kod yazıyorsunuz . İşlemci, performans için bellek erişimini yeniden sipariş etmeyi seçebilir. Bu nedenle, programınızda veri yarışları varsa (birden fazla çekirdek / hiper iş parçacığının aynı anda aynı belleğe erişmesi mümkün olduğunda veri yarışması varsa), işlemci bellek modeline bağımlı olması nedeniyle programınız platformlar arası değildir. İşlemcilerin bellek erişimini nasıl yeniden sipariş edebileceğini öğrenmek için Intel veya AMD yazılım kılavuzlarına başvurabilirsiniz.

Çok önemli olarak, kilitler (ve kilitleme ile eşzamanlı semantikler) genellikle çapraz platformda uygulanır ... Bu nedenle, veri yarışları olmayan çok iş parçacıklı bir programda standart kilitler kullanıyorsanız, çapraz platform bellek modelleri hakkında endişelenmenize gerek yoktur .

İlginç bir şekilde, C için Microsoft derleyiciler uçucu için acquire / bırakma anlambilim C bellek modeli ++ eksikliği ile başa çıkmak için ++ uzantısı C olan var ++ http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs .80) . Ancak, Windows'un yalnızca x86 / x64 üzerinde çalıştığı göz önüne alındığında, bu çok fazla şey söylemez (Intel ve AMD bellek modelleri, bir dilde edinme / bırakma semantiği uygulamayı kolaylaştırır ve verimli hale getirir).


2
Yanıt yazıldığında, Windows'un yalnızca x86 / x64 üzerinde çalıştığı doğrudur, ancak Windows zaman içinde IA64, MIPS, Alpha AXP64, PowerPC ve ARM'de çalışır. Bugün x86'dan oldukça farklı bir bellek olan ARM'nin çeşitli versiyonlarında çalışıyor ve neredeyse hiçbir yerde affedici değil.
Lorenzo Dematté

Bu bağlantı biraz bozuk ( "Visual Studio 2005 Emekli belgeleri" diyor ). Güncellemek ister misiniz?
Peter Mortensen

3
Cevap yazıldığında bile doğru değildi.
Ben

" Eş zamanlı olarak aynı bellek erişimi için bir erişmeye" çakışan bir şekilde
curiousguy

27

Tüm verilerinizi korumak için muteks kullanırsanız, endişelenmenize gerek yoktur. Muteksler her zaman yeterli sipariş ve görünürlük garantisi sağlamıştır.

Şimdi, atomik veya kilitsiz algoritmalar kullandıysanız, bellek modeli hakkında düşünmeniz gerekir. Bellek modeli, atomiklerin ne zaman sipariş ve görünürlük garantileri sağladığını açıklar ve elle kodlanan garantiler için portatif çitler sağlar.

Daha önce, atomik derleyici içselleri veya daha üst düzey bir kütüphane kullanılarak yapılacaktı. Çitler, CPU'ya özgü talimatlar (bellek engelleri) kullanılarak yapılmış olurdu.


19
Daha önce sorun muteks (C ++ standardı açısından) diye bir şey olmamasıydı. Bu nedenle, size verilen tek garanti, kodu taşımadığınız sürece (garantilerde küçük değişikliklerin fark edilmesi zor olduğu sürece) muteks üreticisi tarafından sağlanmıştır. Şimdi platformlar arasında taşınabilir olması gereken standart tarafından sağlanan garantileri alıyoruz.
Martin York

4
@Martin: her durumda, bir şey bellek modelidir, diğeri ise o bellek modelinin üstünde çalışan atomik ve diş açıcı ilkelerdir.
ninjalj

4
Ayrıca, daha önce dilde daha önce dil düzeyinde hiç bellek modeli olmadığı, temel CPU'nun bellek modeli olduğu ortaya çıktı. Şimdi çekirdek dilin bir parçası olan bir bellek modeli var; OTOH, muteksler ve benzerleri her zaman bir kütüphane olarak yapılabilir.
ninjalj

3
Muteks kütüphanesini yazmaya çalışan insanlar için de gerçek bir sorun olabilir . CPU, bellek denetleyicisi, çekirdek, derleyici ve "C kütüphanesi" farklı ekipler tarafından uygulandığında ve bazıları bu şeylerin nasıl çalışması gerektiği konusunda şiddetli bir anlaşmazlık içinde, bazen, biz sistem programcıları hoş bir cephe sunmak için yapmak zorunda uygulama seviyesine hiç hoş değil.
zwol

11
Ne yazık ki, dilinizde tutarlı bir bellek modeli yoksa, veri yapılarınızı basit mutekslerle korumak yeterli değildir. Tek bir iş parçacığı bağlamında anlamlı olan çeşitli derleyici optimizasyonları vardır, ancak birden çok iş parçacığı ve işlemci çekirdeği devreye girdiğinde, bellek erişimlerinin yeniden düzenlenmesi ve diğer optimizasyonlar tanımsız davranışlar verebilir. Daha fazla bilgi için Hans Boehm tarafından "konu kitaplık olarak uygulanması olamaz" bkz citeseer.ist.psu.edu/viewdoc/...
exDM69

0

Yukarıdaki cevaplar C ++ bellek modelinin en temel yönlerini ele almaktadır. Pratikte, std::atomic<>"sadece iş" in çoğu kullanımı , en azından programcı aşırı optimizasyon yapana kadar (örneğin, çok fazla şey rahatlamaya çalışarak).

Hataların hala yaygın olduğu bir yer vardır: dizi kilitleri . Https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf adresindeki zorluklarla ilgili mükemmel ve okunması kolay bir tartışma var . Sıra kilitleri çekici çünkü okuyucu kilit kelimeye yazmaktan kaçınıyor. Aşağıdaki kod, yukarıdaki teknik raporun Şekil 1'ine dayanmaktadır ve C ++ 'da dizi kilitleri uygulanırken karşılaşılan zorlukları vurgular:

atomic<uint64_t> seq; // seqlock representation
int data1, data2;     // this data will be protected by seq

T reader() {
    int r1, r2;
    unsigned seq0, seq1;
    while (true) {
        seq0 = seq;
        r1 = data1; // INCORRECT! Data Race!
        r2 = data2; // INCORRECT!
        seq1 = seq;

        // if the lock didn't change while I was reading, and
        // the lock wasn't held while I was reading, then my
        // reads should be valid
        if (seq0 == seq1 && !(seq0 & 1))
            break;
    }
    use(r1, r2);
}

void writer(int new_data1, int new_data2) {
    unsigned seq0 = seq;
    while (true) {
        if ((!(seq0 & 1)) && seq.compare_exchange_weak(seq0, seq0 + 1))
            break; // atomically moving the lock from even to odd is an acquire
    }
    data1 = new_data1;
    data2 = new_data2;
    seq = seq0 + 2; // release the lock by increasing its value to even
}

İlk başta dikişler kadar sezgisel değil data1ve data2olması gerekiyor atomic<>. Eğer atomik reader()değillerse, yazıldıkları ile aynı anda okunabilirler writer(). C ++ bellek modeline göre, bu veriyi hiç kullanmasa bilereader() bir yarıştır . Ek olarak, atomik değillerse, derleyici bir kayıttaki her değerin ilk okumasını önbelleğe alabilir. Açıkçası bunu istemezsiniz ... whileDöngünün her yinelemesinde yeniden okumak istersiniz reader().

Bunları yapmak atomic<>ve bunlara erişmek de yeterli değildir memory_order_relaxed. Bunun nedeni, seq (in reader()) ' in okumalarının sadece semantik kazanmasıdır . Basit bir ifadeyle, X ve Y bellek erişimleriyse, X Y'den önce gelir, X bir edinme veya bırakma değildir ve Y bir edinme ise, derleyici Y'yi X'ten önce yeniden sıralayabilir. Y, seq ve X'in ikinci okumasıysa Y, verilerin okunmasıydı, böyle bir yeniden sıralama kilit uygulamasını bozacaktır.

Kağıt birkaç çözüm sunuyor. En iyi performans bugün biri muhtemelen bir kullanan biri atomic_thread_fenceile memory_order_relaxed daha önce seqlock ikinci okuma. Bu makalede, Şekil 6. Buradaki kodu çoğaltmıyorum, çünkü bunu okuyan herkes gerçekten okumalı. Bu gönderiye göre daha kesin ve eksiksiz.

Son konu datadeğişkenleri atomik hale getirmenin doğal olmayabileceğidir . Kodunuzda yapamıyorsanız, çok dikkatli olmanız gerekir, çünkü atomik olmayandan atomikliğe döküm sadece ilkel tipler için yasaldır. C ++ 20'nin eklenmesi gerekiyor atomic_ref<>, bu da bu sorunun çözülmesini kolaylaştıracak.

Özetlemek gerekirse: C ++ bellek modelini anladığınızı düşünseniz bile, kendi dizi kilitlerinizi yuvarlamadan önce çok dikkatli olmalısınız.


-2

C ve C ++, iyi biçimlendirilmiş bir programın yürütme izi ile tanımlanırdı.

Şimdi bunların yarısı bir programın yürütme izlemesiyle ve yarısı bir senkronizasyon nesnesindeki birçok sıralamayla tanımlanır.

Yani bu dil tanımları, bu iki yaklaşımı karıştırmak için mantıklı bir yöntem olarak hiçbir anlam ifade etmiyor. Özellikle, bir muteks veya atomik değişkenin yok edilmesi iyi tanımlanmamıştır.


Dil tasarımını iyileştirme konusundaki şiddetli arzunuzu paylaşıyorum, ancak bu davranışın belirli dil tasarım ilkelerini nasıl ihlal ettiğini açıkça ve açıkça gösterdiğiniz basit bir davaya odaklanmış olsaydınız, cevabınızın daha değerli olacağını düşünüyorum. Bundan sonra, bana izin verirseniz, bu cevabın bu noktaların her birinin alaka düzeyi için çok iyi bir argüman vermenizi şiddetle tavsiye ederim, çünkü C ++ tasarımının algıladığı yoğun verimlilik avantajlarının alaka düzeyiyle karşılaştırılacaklar
Matias Haeussler

1
@MatiasHaeussler Cevabımı yanlış okuduğunu düşünüyorum; Ben burada belirli bir C ++ özelliği tanımına itiraz değilim (Ben de burada böyle sivri eleştiriler var ama değil). Burada C ++ (ne de C) iyi tanımlanmış bir yapı olduğunu savunuyorum. Artık MT semantikleri, artık sıralı semantiklere sahip olmadığınız için tam bir karmaşa. (Java MT kırık ama daha az kırık olduğuna inanıyorum.) "Basit örnek" hemen hemen her MT programı olurdu. Kabul etmiyorsanız, MT C ++ programlarının doğruluğunu nasıl kanıtlayacağımla ilgili sorumu yanıtlayabilirsiniz. .
curiousguy

İlginç, bence sorularınızı okuduktan sonra ne demek istediğinizi daha iyi anlıyorum. Haklıysam, C ++ MT programlarının doğruluğu için kanıt geliştirmenin imkansızlığına atıfta bulunuyorsunuz . Böyle bir durumda, bilgisayar programlamasının geleceği için, özellikle yapay zekanın gelişi için benim için çok önemli bir şey olduğunu söyleyebilirim. Ama aynı zamanda, yığın taşmasıyla ilgili sorular soran insanların büyük bir çoğunluğunun farkında oldukları bir şey olmadığını ve hatta ne demek istediğinizi anladıktan ve ilginizi çektikten sonra bile
Matias Haeussler

1
"Bilgisayar programlarının demostrabilitesi ile ilgili sorular stackoverflow'da veya stackexchange'te (her ikisinde de nerede ise) gönderilmeli mi?" Bu meta stackoverflow için bir gibi görünüyor, değil mi?
Matias Haeussler

1
@MatiasHaeussler 1) C ve C ++ temel olarak atomik değişkenlerin, mutekslerin ve çoklu iş parçacığının "bellek modelini" paylaşır. 2) Bunun önemi "bellek modeli" ne sahip olmanın yararları ile ilgilidir. Bence model sağlam olduğu için fayda sıfır.
meraklı
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.