Czy const oznacza thread-safe W C++11?

Słyszałem, że const oznacza thread-safe W C++11 . To prawda?

Czy to znaczy, że constjest teraz odpowiednikiem Java's synchronized?

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

Author: Johannes Schaub - litb, 2013-01-02

1 answers

Słyszałem, że const oznacza thread-safe w C++11 . To prawda?

To jestW pewnym sensie prawda...

To jest to, co standardowy język ma do powiedzenia na temat thread-safety:

[1.10/4] Konflikt dwóch wyrażeń , jeśli jedno z nich modyfikuje lokalizację pamięci (1.7), a drugie 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 jedna nie jest atomowa i żadna nie dzieje się przed drugą. Każdy taki wyścig danych skutkuje niezdefiniowanym zachowaniem.

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

  1. istnieją dwie lub więcej czynności wykonywanych w tym samym czasie na danej rzeczy; i
  2. przynajmniej jeden z nich jest zapisem.

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

[17.6.5.9/1] W tej sekcji określono wymagania, które muszą spełniać implementacje, aby zapobiec wyścigom danych (1.10). Każda funkcja biblioteki standardowej spełnia wszystkie wymagania, o ile nie określono inaczej. Implementacje mogą zapobiegać wyścigom danych w przypadkach innych niż wymienione poniżej.

[17.6.5.9/3] Funkcja biblioteki standardowej C++ nie może bezpośrednio ani pośrednio modyfikować obiektów (1.10) dostępnych przez wątki inne niż bieżący wątek, chyba że obiekty są dostępne bezpośrednio lub pośrednio przez argumenty Nie-const, w tym this.

Co w prostych słowach mówi, że oczekuje operacji na obiektach const, aby były bezpieczne dla wątku. Oznacza to, że biblioteka Standardowa nie wprowadzi wyścig danych tak długo, jak operacje na const obiektach własnego typu albo

  1. składają się w całości z czytań ,czyli 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 składnikiem standardowej biblioteki może spowodować wyścig danych. Podsumowując, const oznacza thread-safe ze standardu Biblioteka punkt widzenia. Ważne jest, aby pamiętać, że jest to tylko kontrakt i nie będzie egzekwowany przez kompilator, jeśli go złamiesz, otrzymasz niezdefiniowane zachowanie i jesteś zdany na siebie. To, czy const jest obecne, czy nie, nie wpłynie na generowanie kodu-przynajmniej nie w odniesieniu do danych --.

Czy to znaczy, że const jest teraz odpowiednikiem Java 's synchronized?

Nie . Nie w wszystkie...

Rozważmy następującą zbyt 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;
    }
};

The member-function area jestthread-safe ; nie dlatego, że jego const, ale dlatego, że składa się wyłącznie z operacji odczytu. Nie ma żadnych zapisów, a przynajmniej jeden zapis jest konieczny, aby doszło do wyścigu danych . Oznacza to, że możesz wywołać area z dowolnej liczby wątków, a otrzymasz poprawne wyniki czas.

Zauważ, że nie oznacza to, że rect jest bezpieczne dla wątków . W rzeczywistości, łatwo zauważyć, jak gdyby wywołanie area miało nastąpić w tym samym czasie, co wywołanie set_size na danym rect, to area może skończyć się obliczaniem jego wyniku na podstawie starej szerokości i nowej wysokości (lub nawet na zniekształconych wartościach).

Ale to jest w porządku, rect nie jest const więc nawet nie oczekuje się, abythread-safe mimo wszystko. Obiekt zadeklarowany const rect, z drugiej strony, byłby thread-safe ponieważ żadne zapisy nie są możliwe (a jeśli rozważasz const_cast - ing coś pierwotnie zadeklarowanego const, to otrzymujesz undefined-behavior i to wszystko).

Więc co to znaczy?

Załóżmy-dla dobra argumentu - że operacje mnożenia są niezwykle kosztowne i lepiej ich unikać, gdy jest to możliwe. Możemy obliczyć obszar tylko wtedy, gdy jest wymagane, a następnie buforować go w przypadku, gdy jest wymagane ponownie w przyszłość:

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żesz mentalnie zastąpić int przez bardzo dużą dynamicznie przydzielaną liczbę całkowitą, która jest z natury nie bezpieczna dla wątku i dla której mnożenie jest niezwykle kosztowne.]

The member-function area nie jest już thread-safe , robi teraz zapisy i nie jest wewnętrznie zsynchronizowany. Czy to jakiś problem? Wywołanie do area może się zdarzyć jako część konstruktora kopiującego z innego obiektu, taki konstruktor mógł być wywołany przez jakąś operację na standardowym kontenerze, i w tym momencie biblioteka standardowa oczekuje, że operacja ta będzie zachowywać się jak odczyt w odniesieniu do wyścigów danych. Ale piszemy!

Jak tylko umieścimy rect wstandardowym kontenerze --bezpośrednio lub pośrednio -- podpisujemy kontrakt Z standardową biblioteką . Aby dalej robić wpisy w Funkcja const nadal honorując ten kontrakt, 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 stworzyliśmy area funkcję thread-safe , ale rect nadal nie jest thread-safe . Wywołanie area dzieje się w tym samym czasie, że wywołanie set_size może nadal kończyć się obliczeniem niewłaściwej wartości, ponieważ przypisania do width i height nie są chronione przez mutex.

If we really wanted a thread-safe rect, we would użyj prymitywu synchronizacji, aby chronić non-thread-safe rect.

Kończą się słowa kluczowe ?

Tak, są. Kończą im się słowa kluczowe Od pierwszego dnia.

źródło: nie wiesz const i mutable - Herb Sutter

 123
Author: K-ballo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 20:53:22