Я чую, що це const
означає, що безпечно для потоків у C ++ 11 . Це правда?
Це дещо правда ...
Ось що має сказати Стандартна мова щодо безпеки потоку:
[1.10 / 4]
Дві оцінки виразів суперечать, якщо одна з них модифікує місце пам'яті (1.7), а інша отримує доступ або змінює одне і те ж місце пам'яті.
[1.10 / 21]
Виконання програми містить перегони даних, якщо вона містить дві суперечливі дії в різних потоках, принаймні одна з яких не є атомною, і жодне не відбувається перед іншим. Будь-яка така гонка даних призводить до невизначеної поведінки.
що є не що інше, як достатня умова для перебігу даних :
- Є дві або більше дій, які виконуються одночасно над даною річчю; і
- Принаймні одна з них - це написання.
Стандартна бібліотека заснована на тому , що, йдучи трохи далі:
[17.6.5.9/1]
У цьому розділі визначено вимоги, яким повинні відповідати реалізації, щоб запобігти перегонам даних (1.10). Кожна стандартна функція бібліотеки повинна відповідати кожній вимозі, якщо не вказано інше. Впровадження може запобігти перегонам даних у випадках, відмінних від зазначених нижче.
[17.6.5.9/3]
C ++ стандартна бібліотечна функція не повинна прямо або побічно змінювати об'єкти (1.10) доступнікрім поточного потокуякщо об'єкти не будуть доступні безпосередньо або побічно через непредставлених роботи функції потоків константних аргументів,тому числіthis
.
що простими словами говорить, що він очікує, що операції над const
об'єктами є безпечними для потоків . Це означає, що Стандартна бібліотека не буде вводити перегони даних, поки операції const
з власними типами також
- Складається повністю з прочитаних - тобто немає записів--; або
- Внутрішньо синхронізує записи.
Якщо це очікування не відповідає одному із ваших типів, то використання його прямо або опосередковано разом із будь-яким компонентом Стандартної бібліотеки може призвести до перегонів даних . На закінчення, const
чи означає безпеку потоків з точки зору « Стандартна бібліотека» . Важливо зауважити, що це лише контракт, і компілятор його не буде виконувати, якщо розірвати його, ви отримаєте не визначену поведінку і ви самі. Незалежно від того const
, присутній чи ні , не впливатиме на генерацію коду --at мірою не щодо гонок даних -.
Чи означає це const
тепер еквівалент Java «з synchronized
?
Ні . Зовсім ...
Розглянемо наступний надто спрощений клас, що представляє прямокутник:
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;
}
};
Член-функція area
є поточно- ; не тому, що його const
, а тому, що він повністю складається з операцій з читання. Жодного запису не бере участь, і принаймні одне записування необхідне, щоб відбувся перегон даних . Це означає, що ви можете зателефонувати area
з будь-якої кількості потоків, скільки вам захочеться, і ви будете отримувати правильні результати весь час.
Зауважте, що це не означає, що rect
це безпечно для потоків . Насправді, легко зрозуміти, як якщо заклик повинен area
відбуватися в той самий час, коли виклик до set_size
заданої дати rect
, то в area
кінцевому підсумку можна обчислити його результат на основі старої ширини та нової висоти (або навіть на зібраних значеннях) .
Але це добре, rect
не const
так вже й не очікується, що він буде безпечним для потоків . Об'єкт, оголошений const rect
, з іншого боку, буде безпечним для потоків, оскільки записи неможливі (і якщо ви думаєте про const_cast
те, що раніше було оголошено щось, const
тоді ви отримуєте не визначене поведінку, і все).
То що ж це означає тоді?
Припустимо - для аргументу - що операції множення є надзвичайно дорогими, і ми краще уникаємо їх, коли це можливо. Ми можемо обчислити область лише за умови, що вона запитується, а потім кешувати її у випадку, якщо вона буде запрошена знову в майбутньому:
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;
}
};
[Якщо цей приклад здається занадто штучним, ви можете подумки замінити int
на дуже велику динамічно розподіляє ціле число , яке по своїй суті, ні поточно- і , для яких множення є надзвичайно дорогими.]
Функція- член area
вже не є безпечною для потоків , вона робить запис зараз і не є внутрішньо синхронізованою. Це проблема? Виклик area
може відбуватися як частина конструктора копіювання іншого об'єкта, такий конструктор міг бути викликаний деякою операцією на стандартному контейнері , і в цей момент стандартна бібліотека очікує, що ця операція поводиться як зчитування стосовно раси даних . Але ми робимо пише!
Щойно ми поміщаємо rect
в стандартний контейнер - прямо чи опосередковано - ми укладаємо договір із Стандартною бібліотекою . Щоб продовжувати робити записи у const
функції, зберігаючи цей контракт, нам потрібно внутрішньо синхронізувати записи:
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;
}
};
Зауважте, що ми зробили area
функцію безпечною для потоків , але rect
все ще не є безпечною для потоків . Заклик, який area
відбувається одночасно, коли виклик set_size
все-таки може закінчитися обчисленням невірного значення, оскільки призначення width
і height
не захищені mutex.
Якби ми справді хотіли захистити нитку rect
, використовували б примітивність синхронізації, щоб захистити захист від потоку rect
.
У них не вистачає ключових слів ?
Так, вони є. У них не вистачає ключових слів з першого дня.
Джерело : Ви не знаєте const
іmutable
- Герб Саттер