Я слышал, что это 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
не защищены мьютексом.
Если бы нам действительно нужна была потокобезопасность rect
, мы бы использовали примитив синхронизации для защиты небезопасных потоков rect
.
У них заканчиваются ключевые слова ?
Да, они. У них заканчиваются ключевые слова с первого дня.
Источник : Вы не знаете const
иmutable
- Херб Саттер