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 const
jest teraz odpowiednikiem Java's synchronized
?
Czy kończą sięsłowa kluczowe ?
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:
- istnieją dwie lub więcej czynności wykonywanych w tym samym czasie na danej rzeczy; i
- 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
- składają się w całości z czytań ,czyli nie ma zapisów--; lub
- 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 'ssynchronized
?
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
.
Tak, są. Kończą im się słowa kluczowe Od pierwszego dnia.Kończą się słowa kluczowe ?
źródło: nie wiesz const
i mutable
- Herb Sutter
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