Czy const oznacza bezpieczeństwo wątków w C ++ 11?

116

Słyszałem, że constoznacza to bezpieczeństwo wątków w C ++ 11 . Czy to prawda?

Czy to znaczy, constjest teraz odpowiednikiem Javy s” synchronized?

Czy kończą się słowa kluczowe ?

K-ballo
źródło
1
C ++ - faq jest generalnie administrowany przez społeczność C ++ i możesz przyjść i zapytać nas o opinie na naszym czacie.
Puppy
@DeadMG: Nie byłem świadomy C ++ - faq i jego etykiety, zasugerowano w komentarzu.
K-ballo
2
Gdzie słyszałeś, że const oznacza bezpieczeństwo wątków?
Mark B
2
@Mark B: Herb Sutter i Bjarne Stroustrup tak mówili na Standard C ++ Foundation , zobacz link na dole odpowiedzi.
K-ballo
UWAGA DLA PRZYCHODZĄCYCH TUTAJ: prawdziwe pytanie NIE dotyczy tego, czy const oznacza bezpieczeństwo wątków. Byłoby to nonsensem, ponieważ w przeciwnym razie oznaczałoby to, że powinieneś być w stanie po prostu przejść dalej i oznaczyć każdą metodę bezpieczną dla wątków jako const. Raczej pytanie, które tak naprawdę zadajemy, jest const IMPLIKOWANE wątkowo bezpieczne i o to właśnie chodzi w tej dyskusji.
user541686

Odpowiedzi:

132

Słyszałem, że constoznacza to bezpieczeństwo wątków w C ++ 11 . Czy to prawda?

To trochę prawda ...

Oto, co język standardowy ma do powiedzenia na temat bezpieczeństwa wątków:

[1.10 / 4] Dwie oceny wyrażeń powodują konflikt, jeśli jedna z nich modyfikuje lokalizację pamięci (1.7), a druga uzyskuje dostęp lub modyfikuje tę samą lokalizację pamięci.

[1.10 / 21] Wykonanie programu zawiera wyścig danych, jeśli zawiera dwie sprzeczne akcje w różnych wątkach, z których przynajmniej jeden nie jest atomowy i żadne nie występuje przed drugim. Każdy taki wyścig danych skutkuje niezdefiniowanym zachowaniem.

co jest niczym innym jak warunkiem wystarczającym do wystąpienia wyścigu danych :

  1. Na danej rzeczy są wykonywane jednocześnie dwie lub więcej czynności; i
  2. Przynajmniej jeden z nich to pismo.

Biblioteka standardowa opiera się na tym, idąc nieco dalej:

[17.6.5.9/1] Ta sekcja określa wymagania, które muszą spełnić implementacje, aby zapobiec wyścigom danych (1.10). Każda standardowa funkcja biblioteczna spełnia każdy wymóg, chyba że określono inaczej. Implementacje mogą zapobiegać wyścigom danych w przypadkach innych niż określone poniżej.

[17.6.5.9/3] Standardowa funkcja biblioteczna C ++ nie może bezpośrednio ani pośrednio modyfikować obiektów (1.10) dostępnych dla wątków innych niż bieżący wątek, chyba że dostęp do obiektów jest uzyskiwany bezpośrednio lub pośrednio za pośrednictwemargumentówinnych niż const , w tymthis.

który w prostych słowach mówi, że oczekuje, że operacje na constobiektach będą bezpieczne dla wątków . Oznacza to, że biblioteka standardowa nie wprowadzi wyścigu danych tak długo, jak operacje na constobiektach własnego typu

  1. Składają się wyłącznie z lektur - to znaczy nie ma zapisów -; lub
  2. Wewnętrznie synchronizuje zapisy.

Jeśli to oczekiwanie nie dotyczy jednego z twoich typów, to użycie go bezpośrednio lub pośrednio razem z dowolnym elementem Biblioteki standardowej może spowodować wyścig danych . Podsumowując, constoznacza bezpieczeństwo wątków z punktu widzenia biblioteki standardowej . Ważne jest, aby pamiętać, że jest to tylko umowa i nie będzie egzekwowana przez kompilator, jeśli ją złamiesz, otrzymasz niezdefiniowane zachowanie i jesteś sam. To, czy constjest obecny, czy nie, wpłynie na generowanie kodu - przynajmniej nie w odniesieniu do wyścigów danych -.

Czy to znaczy, constjest teraz odpowiednikiem Javy s” synchronized?

Nie . Ani trochę...

Rozważmy następującą, nadmiernie uproszczoną klasę reprezentującą prostokąt:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};

Funkcja członkowska area jest bezpieczna wątkowo ; nie dlatego const, że jest, ale dlatego, że składa się wyłącznie z operacji odczytu. Nie ma żadnych zapisów, a przynajmniej jeden zapis jest potrzebny, aby wystąpił wyścig danych . Oznacza to, że możesz dzwonić areaz dowolnej liczby wątków i przez cały czas będziesz otrzymywać prawidłowe wyniki.

Zauważ, że nie oznacza rectto, że jest bezpieczny dla wątków . W rzeczywistości łatwo jest zobaczyć, jak gdyby wywołanie areamiało nastąpić w tym samym czasie, co wywołanie set_sizedanego rect, to areamogłoby zakończyć się obliczeniem jego wyniku na podstawie starej szerokości i nowej wysokości (lub nawet zniekształconych wartości) .

Ale to jest w porządku, rectnie jest constwięc nawet oczekiwane, że będzie w końcu bezpieczne dla wątków . Z const rectdrugiej strony, zadeklarowany obiekt byłby bezpieczny dla wątków, ponieważ żadne zapisy nie są możliwe (a jeśli rozważasz - biorąc pod uwagę const_castcoś pierwotnie zadeklarowanego const, otrzymasz niezdefiniowane zachowanie i to wszystko).

Więc co to znaczy?

Załóżmy - ze względu na argumentację - że operacje mnożenia są niezwykle kosztowne i lepiej ich unikać, jeśli to możliwe. Moglibyśmy obliczyć obszar tylko wtedy, gdy jest żądany, a następnie buforować go na wypadek ponownego zażądania w przyszłości:

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

[Jeśli ten przykład wydaje się zbyt sztuczny, można psychicznie zastąpić intprzez bardzo dużą dynamicznie przydzielonej liczby całkowitej , która jest z natury nie wątku bezpieczny i dla których mnożenia są niezwykle kosztowne.]

Funkcja członkowska area nie jest już bezpieczna dla wątków , teraz wykonuje zapisy i nie jest wewnętrznie synchronizowana. To jest problem? Wywołanie do areamoże nastąpić jako część konstruktora kopiującego innego obiektu, taki konstruktor mógł zostać wywołany przez jakąś operację na standardowym kontenerze iw tym momencie biblioteka standardowa oczekuje, że operacja ta będzie się zachowywać jak odczyt w odniesieniu do wyścigów danych . Ale my piszemy!

Gdy tylko umieścimy plik rectw standardowym kontenerze - bezpośrednio lub pośrednio - zawieramy umowę z Biblioteką Standardową . Aby nadal wykonywać zapisy w constfunkcji, jednocześnie przestrzegając tego kontraktu, musimy wewnętrznie zsynchronizować te zapisy:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );
        
            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );
        
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

Zauważ, że sprawiliśmy, że areafunkcja jest bezpieczna wątkowo , ale rectnadal nie jest bezpieczna wątkowo . Wezwanie do areazdarzenia w tym samym czasie, w którym wywołanie set_sizemoże nadal kończyć się obliczeniem niewłaściwej wartości, ponieważ przypisania do widthi heightnie są chronione przez muteks.

Gdybyśmy naprawdę chcieli mieć bezpieczny rect wątkowo, użylibyśmy operacji podstawowej synchronizacji do ochrony tego, co nie jest bezpieczne dla wątków rect .

Czy kończą się słowa kluczowe ?

Tak, oni są. Od pierwszego dnia brakuje im słów kluczowych .


Źródło : nie wiesz constimutable - Herb Sutter

K-ballo
źródło
6
@Ben Voigt: Rozumiem, że specyfikacja C ++ 11 dla std::stringjest sformułowana w sposób, który już zabrania COW . Nie pamiętam jednak szczegółów ...
K-ballo
3
@BenVoigt: Nie. To po prostu zapobiegałoby niezsynchronizowaniu takich rzeczy - tj. Nie jest bezpieczne wątkowo. C ++ 11 już wyraźnie zakazuje COW - ten konkretny fragment nie ma jednak z tym nic wspólnego i nie blokuje COW.
Puppy
2
Wydaje mi się, że istnieje luka logiczna. [17.6.5.9/3] zabrania „za bardzo”, mówiąc „nie może bezpośrednio ani pośrednio modyfikować”; należy powiedzieć „nie powinien bezpośrednio lub pośrednio wprowadzać wyścig danych”, chyba zapisu atomowa jest gdzieś zdefiniowane nie być „zmodyfikować”. Ale nie mogę tego nigdzie znaleźć.
Andy Prowl,
1
Prawdopodobnie tutaj wyjaśniłem cały mój punkt widzenia : isocpp.org/blog/2012/12/… Dziękuję za próbę pomocy.
Andy Prowl,
1
czasami zastanawiam się, kto był (lub osoby bezpośrednio zaangażowane) faktycznie odpowiedzialny za spisanie niektórych standardowych akapitów, takich jak te.
pepper_chico