Pytanie C ++ 11 wprowadził ustandaryzowany model pamięci. Co to znaczy? I jak to wpłynie na programowanie C ++?


C ++ 11 wprowadził ustandaryzowany model pamięci, ale co to dokładnie oznacza? I jak to wpłynie na programowanie C ++?

Ten artykuł (przez Gavin Clarke który cytuje Herb Sutter) mówi że,

Model pamięci oznacza kod C ++   teraz ma znormalizowaną bibliotekę do wywoływania   niezależnie od tego, kto wykonał kompilator   i na jakiej platformie działa.   Istnieje standardowy sposób kontrolowania sposobu   różne wątki rozmawiają z   pamięć procesora.

"Kiedy mówisz o dzieleniu   [kod] w różnych rdzeniach   w standardzie mówimy o tym   model pamięci. Idziemy do   zoptymalizuj go, nie łamiąc   następujące założenia ludzie idą   zrobić w kodzie, " Sutter powiedziany.

Więc mogę zapamiętać ten i podobne akapity dostępne w Internecie (jak już miałem swój własny model pamięci od urodzenia: P) i mogą nawet zamieszczać odpowiedzi na pytania zadawane przez innych, ale szczerze mówiąc, nie rozumiem tego dokładnie.

Tak więc, w zasadzie chcę wiedzieć, że programiści C ++ używali do tworzenia aplikacji wielowątkowych jeszcze wcześniej, więc jak to ma znaczenie, czy są to wątki POSIX, czy wątki Windows, czy wątki C ++ 11? Jakie są korzyści? Chcę zrozumieć szczegóły niskiego poziomu.

Mam również wrażenie, że model pamięci C ++ 11 jest w jakiś sposób powiązany z obsługą wielowątkowości w C ++ 11, ponieważ często widzę te dwa elementy razem. Jeśli tak, to jak dokładnie? Dlaczego mieliby być spokrewnieni?

Ponieważ nie wiem, jak działają zespoły wielowątkowości i jaki ogólnie jest model pamięci, pomóżcie mi zrozumieć te pojęcia. :-)


1555
2018-06-11 23:30


pochodzenie




Odpowiedzi:


Najpierw musisz nauczyć się myśleć jak prawnik językowy.

Specyfikacja C ++ nie odnosi się do żadnego konkretnego kompilatora, systemu operacyjnego ani procesora. Odwołuje się do abstrakcyjna maszyna to jest uogólnienie rzeczywistych systemów. W świecie Language Lawyer zadaniem programisty jest napisanie kodu dla abstrakcyjnej maszyny; zadaniem kompilatora jest zaktualizowanie tego kodu na konkretnej maszynie. Kodując sztywno do specyfikacji, możesz mieć pewność, że Twój kod będzie kompilowany i uruchamiany bez modyfikacji w żadnym systemie za pomocą zgodnego kompilatora C ++, czy to dzisiaj, czy za 50 lat.

Maszyna abstrakcyjna w specyfikacji C ++ 98 / C ++ 03 jest zasadniczo jednowątkowa. Nie jest więc możliwe pisanie wielowątkowego kodu C ++, który jest "w pełni przenośny" w odniesieniu do specyfikacji. Spec nie mówi nic na temat atomowość pamięci i sklepów lub zamówienie w którym mogą się dziać ładunki i magazyny, nie mówiąc o rzeczach takich jak muteksy.

Oczywiście w praktyce można pisać wielowątkowy kod dla konkretnych systemów betonowych - takich jak pthreads lub Windows. Ale tam nie ma standardsposób na napisanie wielowątkowego kodu dla C ++ 98 / C ++ 03.

Abstrakcyjna maszyna w C ++ 11 ma wielowątkową konstrukcję. Ma również dobrze zdefiniowaną model pamięci; to znaczy, mówi, co może i czego nie może kompilator, jeśli chodzi o dostęp do pamięci.

Rozważmy następujący przykład, w którym para zmiennych globalnych jest dostępna jednocześnie przez dwa wątki:

           Global
           int x, y;

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

Co może wyjść z wątku 2?

W C ++ 98 / C ++ 03 nie jest to nawet niezdefiniowane zachowanie; samo pytanie jest bez znaczenia ponieważ standard nie kontempluje niczego zwanego "wątkiem".

W C ++ 11 wynikiem jest niezdefiniowane zachowanie, ponieważ ładunki i magazyny nie muszą być ogólnie atomowe. Co może nie wydawać się znaczącym ulepszeniem ... I samo w sobie, nie jest.

Ale w C ++ 11 możesz napisać to:

           Global
           atomic<int> x, y;

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

Teraz sprawy stają się o wiele bardziej interesujące. Po pierwsze, zachowanie tutaj jest określone. Wątek 2 mógł teraz zostać wydrukowany 0 0 (jeśli działa przed wątkiem 1), 37 17 (jeśli działa po wątku 1), lub 0 17 (jeśli działa po wątku 1 przypisuje do x, ale przed przypisuje do y).

Czego nie można wydrukować 37 0, ponieważ domyślnym trybem dla ładunków / zapasów atomowych w C ++ 11 jest wymuszanie sekwencyjna spójność. Oznacza to po prostu, że wszystkie ładunki i magazyny muszą być "tak, jakby" miały miejsce w kolejności, w jakiej zostały napisane w każdym wątku, podczas gdy operacje między wątkami mogą być przeplatane, ale system lubi. Zatem domyślne zachowanie atomów zapewnia oba atomowość i zamawianie dla ładunków i sklepów.

Teraz, na nowoczesnym procesorze, zapewnienie spójności sekwencyjnej może być kosztowne. W szczególności kompilator prawdopodobnie będzie emitował pełnowartościowe bariery pamięci między każdym dostępem tutaj. Ale jeśli twój algorytm może tolerować ładunki i magazyny poza zamówieniem; tj. jeśli wymaga atomowości, ale nie zamawia; tj. jeśli może tolerować 37 0 jako wynik tego programu, możesz napisać to:

           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;

Im bardziej nowoczesny procesor, tym większe prawdopodobieństwo, że będzie on szybszy niż w poprzednim przykładzie.

Wreszcie, jeśli chcesz po prostu zachować określone ładunki i sklepy w kolejności, możesz napisać:

           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;

To zabierze nas z powrotem do zamówionych ładunków i sklepów - tak 37 0 nie jest już możliwym wyjściem - ale robi to przy minimalnym obciążeniu. (W tym trywialnym przykładzie wynik jest taki sam, jak pełna sekwencyjna spójność, w większym programie nie byłaby.)

Oczywiście, jeśli jedynymi wyjściami, które chcesz zobaczyć, są 0 0 lub 37 17, możesz po prostu owinąć muteks wokół oryginalnego kodu. Ale jeśli przeczytałeś tak daleko, założę się, że już wiesz, jak to działa, a ta odpowiedź jest już dłuższa niż zamierzałem :-).

Tak więc, bottom line. Mutexy są świetne, a C ++ 11 je standaryzuje. Ale czasami ze względów wydajnościowych potrzebujesz prymitywów niższego poziomu (np. Klasyka podwójnie sprawdzany wzór blokowania). Nowy standard udostępnia gadżety wysokiego poziomu, takie jak muteksy i zmienne warunkowe, a także dostarcza gadżety niskiego poziomu, takie jak typy atomowe i różne smaki bariery pamięci. Teraz możesz pisać wyrafinowane, wysokowydajne, współbieżne procedury całkowicie w języku określonym przez standard i możesz być pewien, że twój kod będzie się kompilował i działał w niezmienionej postaci zarówno na dzisiejszych systemach, jak i na jutro.

Chociaż szczerze mówiąc, chyba że jesteś ekspertem i pracujesz nad poważnym kodem niskiego poziomu, powinieneś raczej trzymać się muteksów i zmiennych warunkowych. Właśnie to zamierzam zrobić.

Aby uzyskać więcej informacji na ten temat, zobacz ten wpis na blogu.


1803
2018-06-12 00:23



Dobra odpowiedź, ale tak naprawdę błagam o rzeczywiste przykłady nowych prymitywów. Myślę również, że porządkowanie pamięci bez elementów pierwotnych jest takie samo jak przed C ++ 0x: nie ma żadnych gwarancji. - John Ripley
@John: Wiem, ale sam wciąż uczę się prymitywów :-). Myślę też, że gwarantują dostęp bajtowy są atomowe (choć nie uporządkowane), dlatego wybrałam "char" na mój przykład ... Ale nie jestem nawet w 100% pewien ... Jeśli chcesz zasugerować coś dobrego " tutorial "odniesienia dodam je do mojej odpowiedzi - Nemo
@Nawaz: Tak! Dostęp do pamięci może zostać zmieniony przez kompilator lub procesor. Pomyśl o (np.) Pamięciach podręcznych i obciążeniach spekulacyjnych. Kolejność, w jakiej trafia pamięć systemowa, może nie być taka, jak kodowałeś. Kompilator i procesor zapewnią, że takie ponowne zamówienia nie zostaną przerwane jednowątkowy kod. W przypadku kodu wielowątkowego "model pamięci" charakteryzuje możliwe zmiany kolejności i co się dzieje, gdy dwa wątki odczytują / zapisują tę samą lokalizację w tym samym czasie i jak można kontrolować oba. W przypadku kodu jednowątkowego model pamięci jest nieistotny. - Nemo
@Nawaz, @Nemo - Niewielki szczegół: nowy model pamięci ma znaczenie w kodzie jednowątkowym, ponieważ określa niezdefiniowane wyrażenia, takie jak i = i++. Stara koncepcja punkty sekwencji został odrzucony; nowy standard określa to samo przy użyciu a zsekwencjonowane przed relacja, która jest tylko szczególnym przypadkiem bardziej ogólnego połączenia między wątkami dzieje się przedtem pojęcie. - JohannesD
@ AJG85: Sekcja 3.6.2 projektu specyfikacji C ++ 0x mówi: "Zmienne o statycznym czasie przechowywania (3.7.1) lub czasie przechowywania nici (3.7.2) powinny być inicjowane zerami (8.5) przed jakimkolwiek innym zainicjowaniem miejsce." Ponieważ X, y są w tym przykładzie globalne, mają statyczny czas przechowywania i dlatego, jak sądzę, zostaną zinicjalizowane. - Nemo


Po prostu podam analogię, z którą rozumiem modele spójności pamięci (lub w skrócie modele pamięci). Inspiracją jest nowatorska praca Leslie Lamport "Czas, zegary i porządkowanie zdarzeń w systemie rozproszonym". Analogia jest trafna i ma fundamentalne znaczenie, ale dla wielu osób może być przesadą. Mam jednak nadzieję, że dostarczy obraz mentalny (obrazowe przedstawienie), który ułatwia rozumowanie na temat modeli spójności pamięci.

Spójrzmy na historię wszystkich lokalizacji pamięci na wykresie czasoprzestrzennym, w którym oś pozioma reprezentuje przestrzeń adresową (tj. Każda lokalizacja pamięci jest reprezentowana przez punkt na tej osi), a oś pionowa reprezentuje czas (zobaczymy, że ogólnie nie ma uniwersalnego pojęcia czasu). Historia wartości przechowywanych przez każdą lokalizację pamięci jest zatem reprezentowana przez pionową kolumnę pod tym adresem pamięci. Każda zmiana wartości jest spowodowana przez jeden z wątków zapisujących nową wartość do tej lokalizacji. Według a obraz pamięcibędziemy oznaczać agregację / kombinację wartości wszystkich obserwowanych lokalizacji pamięci w określonym czasie przez określony wątek.

Cytując z "Primer spójności pamięci i spójności pamięci podręcznej"

Intuicyjny (i najbardziej restrykcyjny) model pamięci to konsystencja sekwencyjna (SC), w której wielowątkowe wykonywanie powinno wyglądać jak przeplatanie sekwencyjnych wykonań każdego wątku składowego, tak jakby wątki były multipleksowane w czasie na procesorze jednordzeniowym.

Ta globalna kolejność pamięci może się różnić w zależności od przebiegu programu i może nie być znana wcześniej. Charakterystyczną cechą SC jest zbiór poziomych przekrojów reprezentujących diagram adres-czasoprzestrzeń płaszczyzny równoczesności (tj. obrazy pamięci). Na danej płaszczyźnie wszystkie jej zdarzenia (lub wartości pamięci) są równoczesne. Istnieje pojęcie Czas bezwzględny, w którym wszystkie wątki zgadzają się, które wartości pamięci są jednoczesne. W SC za każdym razem jest tylko jeden obraz pamięci udostępniony przez wszystkie wątki. Oznacza to, że w każdej chwili wszystkie procesory zgadzają się z obrazem pamięci (tj. Z zagregowaną zawartością pamięci). Oznacza to nie tylko, że wszystkie wątki wyświetlają tę samą sekwencję wartości dla wszystkich lokalizacji pamięci, ale także, że wszystkie procesory obserwują to samo kombinacje wartości wszystkich zmiennych. Jest to równoznaczne z tym, że wszystkie operacje związane z pamięcią (we wszystkich lokalizacjach pamięci) są obserwowane w tej samej kolejności przez wszystkie wątki.

W swobodnych modelach pamięci każda nić będzie przedzierać czas adresowy w czasie, jedynym ograniczeniem jest to, że fragmenty każdej nitki nie będą się krzyżować, ponieważ wszystkie wątki muszą zgadzać się z historią każdej poszczególnej lokalizacji pamięci (oczywiście kawałki różnych wątków mogą i będą krzyżować się ze sobą). Nie ma uniwersalnego sposobu, aby go podzielić (bez uprzywilejowanego foliowania czasu w przestrzeni adresowej). Plasterki nie muszą być płaskie (lub liniowe). Mogą być zakrzywione i to właśnie sprawia, że ​​wątek odczytuje wartości zapisane inną nitką z porządku, w jakim zostały zapisane. Historie różnych lokalizacji pamięci mogą przesuwać się (lub rozciągać) dowolnie względem siebie oglądane przez dowolny wątek. Każda nić będzie miała inne znaczenie tego, które zdarzenia (lub równoważnie wartości pamięci) są równoczesne. Zestaw zdarzeń (lub wartości pamięci), które są jednocześnie w jednym wątku, nie są równoczesne z innym. Tak więc w luźnym modelu pamięci wszystkie wątki wciąż obserwują tę samą historię (tj. Sekwencję wartości) dla każdej lokalizacji pamięci. Mogą one jednak obserwować różne obrazy pamięci (tj. Kombinacje wartości wszystkich lokalizacji pamięci). Nawet jeśli dwie różne lokalizacje pamięci są zapisywane przez ten sam wątek w sekwencji, dwie nowo zapisane wartości mogą być obserwowane w innej kolejności przez inne wątki.

[Zdjęcie z Wikipedii] Picture from Wikipedia

Czytelnicy znają Einsteina Specjalna teoria względności zauważy, o co mi chodzi. Przetłumaczenie słów Minkowskiego na dziedzinę modeli pamięci: przestrzeń i czas adresu to cienie adresu-czasoprzestrzeni-czasu. W takim przypadku każdy obserwator (tj. Wątek) będzie wyświetlał cienie zdarzeń (tj. Zapasy pamięci / obciążenia) na swojej własnej linii światowej (tj. Jego osi czasu) i własnej płaszczyźnie jednoczesności (jego oś przestrzeni adresowej) . Wątki w modelu pamięci C ++ 11 odpowiadają obserwatorzy które poruszają się względem siebie w szczególnej teorii względności. Sekwencyjna spójność odpowiada Galilejski czasoprzestrzeń (tj. wszyscy obserwatorzy zgadzają się co do jednej absolutnej kolejności zdarzeń i globalnego poczucia symultaniczności).

Podobieństwo między modelami pamięci i szczególną teorią względności wynika z faktu, że oba definiują częściowo uporządkowany zestaw zdarzeń, często nazywany zestawem przyczynowym. Niektóre zdarzenia (tj. Pamięci) mogą wpływać (ale nie wpływać na nie) na inne zdarzenia. Wątek C ++ 11 (lub obserwator w fizyce) jest niczym więcej niż łańcuchem (tj. Całkowicie uporządkowanym zbiorem) zdarzeń (np. Pamięć ładuje się i przechowuje do potencjalnie różnych adresów).

W względności, pewien porządek przywracany jest do pozornie chaotycznego obrazu wydarzeń częściowo uporządkowanych, ponieważ jedyny czasowy porządek, który wszyscy obserwatorzy zgadzają się, jest uporządkowaniem wśród "czasowych" zdarzeń (tj. Wydarzeń, które w zasadzie można łączyć z każdą cząstką wolniejszą niż prędkość światła w próżni). Jedynie uporządkowane wydarzenia w czasie są niezmiennie uporządkowane. Czas w fizyce, Craig Callender.

W modelu pamięci C ++ 11 podobny mechanizm (model spójności) jest wykorzystywany do ich ustalenia lokalne związki przyczynowe.

Aby podać definicję spójności pamięci i motywację do porzucenia SC, przytoczę cytat "Primer spójności pamięci i spójności pamięci podręcznej"

W przypadku maszyny z pamięcią dzieloną model spójności pamięci określa architektonicznie widoczne zachowanie jego systemu pamięci. Kryterium poprawności zachowania pojedynczych partycji rdzenia procesora między "jeden poprawny wynik" i "wiele niepoprawnych alternatyw". Dzieje się tak dlatego, że architektura procesora wymaga, aby wykonanie wątku przekształciło dany stan wejściowy w pojedynczy, dobrze zdefiniowany stan wyjściowy, nawet w rdzeniu poza kolejnością. Modele spójności pamięci wspólnej dotyczą jednak obciążeń i zasobów wielu wątków i zwykle pozwalają na to wiele poprawnych egzekucji jednocześnie uniemożliwiając wiele (więcej) niepoprawnych. Możliwość wielokrotnych poprawnych wykonań wynika z tego, że ISA pozwala na równoczesne wykonywanie wielu wątków, często z wieloma możliwymi legalnymi przeplotami instrukcji z różnych wątków.

Zrelaksowany lub słaby Modele spójności pamięci są motywowane faktem, że większość uporządkowanych pamięci w silnych modelach jest niepotrzebna. Jeśli wątek aktualizuje dziesięć elementów danych, a następnie flagę synchronizacji, programiści zwykle nie dbają o to, czy elementy danych są aktualizowane w kolejności względem siebie, ale tylko o to, że wszystkie elementy danych są aktualizowane przed zaktualizowaniem flagi (zwykle realizowane przy użyciu instrukcji FENCE ). Rozluźnione modele starają się uchwycić tę zwiększoną elastyczność zamówień i zachować tylko zamówienia, które programiści "wymagać"Aby uzyskać wyższą wydajność i poprawność SC. Na przykład w niektórych architekturach bufory zapisu FIFO są używane przez każdy rdzeń do przechowywania wyników zatwierdzonych (wycofanych) sklepów przed zapisaniem wyników w pamięciach podręcznych. Ta optymalizacja poprawia wydajność, ale narusza SC. Bufor zapisu ukrywa opóźnienie obsługi brakującej pamięci. Ponieważ sklepy są powszechne, ważna jest możliwość uniknięcia zgniatania na większości z nich. W przypadku procesora jednordzeniowego bufor zapisu może być niewidoczny architektonicznie, zapewniając, że obciążenie adresu A zwróci wartość ostatniego magazynu do A, nawet jeśli jeden lub więcej sklepów do A znajduje się w buforze zapisu. Zwykle wykonuje się to przez pominięcie wartości ostatniego magazynu do A do obciążenia z A, gdzie "najnowsze" jest określane przez kolejność programów lub przez zatrzymanie obciążenia A, jeśli magazyn na A znajduje się w buforze zapisu . Gdy używanych jest wiele rdzeni, każdy będzie miał swój własny blokujący bufor zapisu. Bez buforów zapisu sprzętem jest SC, ale z buforami zapisu, tak nie jest, co sprawia, że ​​bufor zapisu jest architektonicznie widoczny w procesorze wielordzeniowym.

Przechowywanie magazynów sklepu może się zdarzyć, jeśli rdzeń ma bufor zapisu inny niż FIFO, który pozwala sklepom odejść w innej kolejności niż kolejność, w której zostały wprowadzone. Może się tak zdarzyć, jeśli pierwszy magazyn zostanie pominięty w pamięci podręcznej, gdy drugie trafi lub gdy drugi sklep może się połączyć z wcześniejszym sklepem (tj. Przed pierwszym sklepem). Przeładowywanie obciążenia może również wystąpić w dynamicznie zaplanowanych rdzeniach, które wykonują polecenia poza kolejnością programów. To może zachowywać się tak samo, jak zmiana kolejności sklepów na innym rdzeniu (czy możesz wymyślić przykładowe przeplatanie między dwoma wątkami?). Zmiana kolejności wcześniejszego obciążenia na późniejszy magazyn (zmiana kolejności ładowań) może powodować wiele nieprawidłowych zachowań, takich jak ładowanie wartości po zwolnieniu blokady, która ją chroni (jeśli sklep jest operacją odblokowania). Zwróć uwagę, że ponowne zamówienia w sklepie mogą również powstać z powodu lokalnego ominięcia w powszechnie stosowanym buforze zapisu FIFO, nawet z rdzeniem, które wykonuje wszystkie instrukcje w kolejności programu.

Ponieważ spójność pamięci podręcznej i spójność pamięci są czasami mylone, pouczające jest również posiadanie tego cytatu:

W przeciwieństwie do konsystencji, spójność pamięci podręcznej nie jest widoczne dla oprogramowania ani wymagane. Coherence dąży do tego, aby pamięci podręczne systemu pamięci współużytkowanej były funkcjonalnie niewidoczne jako pamięci podręczne w systemie jednordzeniowym. Prawidłowa spójność zapewnia, że ​​programista nie może określić, czy i gdzie system ma pamięci podręczne, analizując wyniki obciążeń i zapasów. Dzieje się tak, ponieważ poprawna spójność zapewnia, że ​​pamięci podręczne nigdy nie włączają nowych lub innych funkcjonalny zachowanie (programiści mogą nadal być w stanie wywnioskować prawdopodobną strukturę pamięci podręcznej za pomocą wyczucie czasu Informacja). Głównym celem protokołów koherencji pamięci podręcznej jest utrzymanie niezmiennika niezmiennego dla wielu zapisów (SWMR) dla każdej lokalizacji pamięci.   Ważną różnicą między spójnością a spójnością jest to, że spójność jest określona na a lokalizacja na podstawie pamięci, podczas gdy spójność jest określona w odniesieniu do wszystko lokalizacje pamięci.

Kontynuując nasz obraz mentalny, niezmiennik SWMR odpowiada wymogowi fizycznemu, że najwyżej jedna cząstka znajduje się w dowolnym miejscu, ale może istnieć nieograniczona liczba obserwatorów w dowolnym miejscu.


281
2017-08-29 20:42



+1 dla analogii ze szczególną teorią względności, próbowałem dokonać tej samej analogii osobiście. Zbyt często widzę programiści badający kodowany wątek próbujący zinterpretować zachowanie jako operacje w różnych wątkach, które są przeplatane ze sobą w określonej kolejności, i muszę im powiedzieć, nie, że w systemach wieloprocesorowych pojęcie równoczesności między różnymi <s > Wątki odniesienia </ s> są teraz bez znaczenia. Porównanie ze szczególną teorią względności jest dobrym sposobem na uszanowanie złożoności problemu. - Pierre Lebeaupin
@Ahmed Nassar: link, który dzieliłeś ze Stanford, jest martwy. - Joze
@Joze: Dzięki. Odwołałem się do biblioteki ACM. Jest nadal dostępny bezpłatnie w innym miejscu w Internecie. - Ahmed Nassar
Więc czy powinieneś dojść do wniosku, że Wszechświat jest wielordzeniowy? - Peter K
@PeterK: Dokładnie :) A oto bardzo ładna wizualizacja tego obrazu czasu przez fizyka Briana Greene'a: youtube.com/watch?v=4BjGWLJNPcA&t=22m12s  To jest "The Illusion of Time [Full Documentary]" w minutę 22 i 12 sekund. - Ahmed Nassar


Jest to obecnie kilkuletnie pytanie, ale będąc bardzo popularnym, warto wspomnieć o fantastycznym źródle do nauki o modelu pamięci C ++ 11. Nie ma sensu podsumowywać jego przemówienia, aby uczynić z tego jeszcze jedną pełną odpowiedź, ale biorąc pod uwagę, że to facet, który faktycznie napisał standard, myślę, że warto go obejrzeć.

Herb Sutter ma trzygodzinną rozmowę na temat modelu pamięci C ++ 11 zatytułowanego "Atomowe bronie", dostępnego na stronie Channel9 - Część 1 i część 2. Dyskusja jest dość techniczna i obejmuje następujące tematy:

  1. Optymalizacje, rasy i model pamięci
  2. Zamawianie - Co: kup i wypuść
  3. Zamawianie - Jak: Muteksy, Atomika i / lub Ogrodzenia
  4. Inne ograniczenia kompilatorów i sprzętu
  5. Kod Gen & Performance: x86 / x64, IA64, POWER, ARM
  6. Zrelaksowana Atomika

Dyskusja nie rozwija się na temat API, ale raczej na temat rozumowania, tła, pod maską i poza sceną (czy wiesz, że swobodna semantyka została dodana do standardu tylko dlatego, że POWER i ARM nie obsługują zsynchronizowanego obciążenia wydajnie?).


80
2017-12-20 13:22



Ta rozmowa jest naprawdę fantastyczna, całkowicie warta 3 godzin spędzonych na oglądaniu. - ZunTzu
@ZunTzu: w przypadku większości odtwarzaczy wideo prędkość można ustawić na 1,25, 1,5 lub nawet 2 razy w stosunku do oryginału. - Christian Severin
Czy zdarzyło Ci się, że masz slajdy? linki na stronie kanału 9 nie działają. - athos
@athos Nie mam ich, przepraszam. Spróbuj skontaktować się z kanałem 9, nie sądzę, że usunięcie było zamierzone (domyślam się, że dostali link od Herb Sutter, opublikowany jak jest, a on później usunął pliki, ale to tylko spekulacja ...). - eran


Oznacza to, że standard definiuje teraz wielowątkowość i definiuje to, co dzieje się w kontekście wielu wątków. Oczywiście ludzie używają różnych implementacji, ale to jest pytanie, dlaczego powinniśmy mieć std::string kiedy wszyscy moglibyśmy używać walcowania w domu string klasa.

Kiedy mówimy o wątkach POSIX lub wątkach Windows, to jest to trochę złudzenie, ponieważ faktycznie mówimy o wątkach x86, ponieważ jest to funkcja sprzętowa do współbieżnego działania. Model pamięci C ++ 0x daje gwarancje, niezależnie od tego, czy korzystasz z x86, czy ARM, lub MIPSlub cokolwiek innego, co wymyślisz.


68
2018-06-11 23:42



Nici Posix nie są ograniczone do x86. Rzeczywiście, pierwsze systemy, w których zostały wdrożone, prawdopodobnie nie były systemami x86. Gwinty Posix są niezależne od systemu i są ważne na wszystkich platformach Posix. Nie jest również prawdą, że jest to właściwość sprzętowa, ponieważ wątki Posix mogą być również implementowane poprzez wielozadaniowość kooperacyjną. Ale oczywiście większość problemów związanych z wątkami dotyczy tylko implementacji wątków sprzętowych (a niektóre nawet w systemach wieloprocesorowych / wielordzeniowych). - celtschk


W przypadku języków, które nie określają modelu pamięci, wpisujesz kod języka i model pamięci określony przez architekturę procesora. Procesor może zmienić kolejność dostępu do pamięci na wydajność. Więc, jeśli twój program ma wyścigi danych (wyścig danych odbywa się, gdy możliwe jest jednoczesne korzystanie z wielu rdzeni / hyper-wątków z tej samej pamięci), a następnie program nie jest oparty na wielu platformach ze względu na zależność od modelu pamięci procesora. Możesz zajrzeć do podręczników oprogramowania Intel lub AMD, aby dowiedzieć się, w jaki sposób procesory mogą zmienić kolejność dostępu do pamięci.

Co ważne, semantyka blokująca (i semantyka współbieżna z blokowaniem) są zwykle implementowane w sposób krzyżowy ... Więc jeśli używasz standardowych blokad w programie wielowątkowym bez wyścigów danych, to nie musisz się martwić o modele pamięci na wielu platformach.

Co ciekawe, kompilatory Microsoftu dla C ++ mają semantykę nabycia / wydania dla volatile, która jest rozszerzeniem C ++, aby poradzić sobie z brakiem modelu pamięci w C ++ http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx. Jednakże, biorąc pod uwagę, że system Windows działa tylko na architekturze x86 / x64, niewiele to mówi (modele pamięci Intel i AMD ułatwiają implementację semantyki nabycia / wydania w języku).


49
2017-07-26 04:27



Prawdą jest, że gdy odpowiedź została napisana, system Windows działał tylko na x86 / x64, ale system Windows działał, w pewnym momencie, na IA64, MIPS, Alpha AXP64, PowerPC i ARM. Dziś działa na różnych wersjach ARM, co jest zupełnie inną pamięcią od x86, a nigdzie nie jest tak wybaczający. - Lorenzo Dematté
Ten link jest nieco uszkodzony (mówi "Dokumentacja wycofana z programu Visual Studio 2005"). Chcesz go zaktualizować? - Peter Mortensen
Nie było tak nawet wtedy, gdy odpowiedź została napisana. - Ben
"aby uzyskać dostęp do tej samej pamięci jednocześnie"aby uzyskać dostęp w sprzeczny droga - curiousguy


Jeśli używasz muteksów do ochrony wszystkich danych, naprawdę nie powinieneś się martwić. Mutexy zawsze zapewniały wystarczające gwarancje dotyczące zamawiania i widoczności.

Teraz, jeśli użyłeś atomów lub algorytmów bez blokady, musisz pomyśleć o modelu pamięci. Model pamięci dokładnie opisuje, kiedy atomy zapewniają gwarancje porządku i widoczności oraz zapewnia przenośne ogrodzenia dla gwarancji kodowanych ręcznie.

Wcześniej atomizacja byłaby wykonywana przy użyciu wbudowanych kompilatorów lub niektórych bibliotek wyższych poziomów. Ogrodzenia zostałyby wykonane przy użyciu instrukcji specyficznych dla procesora (bariery pamięci).


22
2018-06-11 23:49



Problemem wcześniej było to, że nie było czegoś takiego jak muteks (pod względem standardu C ++). Tak więc jedyne zapewnione gwarancje zostały dostarczone przez producenta mutex, co było w porządku, o ile nie przesyłasz kodu (niewielkie zmiany w gwarancji są trudne do wykrycia). Teraz otrzymujemy gwarancje zapewnione przez standard, które powinny być przenośne między platformami. - Martin York
@Martin: w każdym razie jedna rzecz to model pamięci, a druga to prymitywy atomów i wątków, które działają na tym modelu pamięci. - ninjalj
Ponadto, moim punktem było przede wszystkim to, że wcześniej w większości nie było modelu pamięci na poziomie językowym, był to model pamięci bazowego procesora. Teraz istnieje model pamięci, który jest częścią podstawowego języka; OTOH, muteksy itp. Zawsze można było zrobić jako bibliotekę. - ninjalj
Może to być również poważny problem dla osób próbujących pisać biblioteka mutex. Kiedy procesor, kontroler pamięci, jądro, kompilator i biblioteka "C" są zaimplementowane przez różne zespoły, a niektóre z nich są w gwałtownej nieporozumieniu co do tego, jak te rzeczy mają działać, cóż, czasami te rzeczy my, systemy, których programiści muszą zrobić, aby zaprezentować ładną fasadę do poziomu aplikacji, nie jest wcale przyjemna. - zwol
Niestety nie wystarczy chronić struktury danych za pomocą prostych muteksów, jeśli w twoim języku nie ma spójnego modelu pamięci. Istnieją różne optymalizacje kompilacji, które mają sens w kontekście pojedynczego wątku, ale gdy wchodzi wiele wątków i rdzeni procesora, zmiana kolejności dostępów do pamięci i inne optymalizacje mogą spowodować niezdefiniowane zachowanie. Aby uzyskać więcej informacji, zobacz "Wątki nie mogą być zaimplementowane jako biblioteka" Hansa Boehm: citeseer.ist.psu.edu/viewdoc/... - exDM69