Я слышал, что const означает потокобезопасный в C++11. Это правда?
Означает ли это, что const теперь эквивалентно synchronized в Java?
У них заканчиваются ключевые слова?
Я слышал, что const означает потокобезопасный в C++11. Это правда?
Означает ли это, что const теперь эквивалентно synchronized в Java?
У них заканчиваются ключевые слова?
Я слышал, что
constозначает потокобезопасный в C++11. Это правда?
Это отчасти верно...
Вот что говорит Standard Language о безопасности потоков:
[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 или нет, это не повлияет на генерацию кода — по крайней мере, не в отношении гонок данных—.
Означает ли это, что
constтеперь эквивалентноsynchronizedв Java?
Нет. Нисколько...
Рассмотрим следующий чрезмерно упрощенный класс, представляющий прямоугольник:
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, вы получите undefined-behavior< /em> и все).
Так что же это значит?
Давайте предположим — в качестве аргумента — что операции умножения чрезвычайно затратны, и нам лучше избегать их, когда это возможно. Мы могли бы вычислить область, только если она запрашивается, а затем кэшировать ее на случай, если она будет запрошена снова в будущем:
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 - Херб Саттер
std::string копирования при записи с подсчетом ссылок, безопасность потоков которых обсуждалась в недавнем вопросе, поскольку std::string::string(const std:string& other) не разрешено изменять счетчик использования в other. Вы согласны? Какие последствия есть для std::shared_ptr?
- person Ben Voigt; 02.01.2013
std::string сформулирована таким образом, что уже запрещает COW. Хотя подробностей не помню...
- person K-ballo; 02.01.2013
r.width() * r.height()) никогда не будет потокобезопасным, даже если объект внутренне потокобезопасен.
- person StackedCrooked; 02.01.2013
const изменяет переменную mutable, то она действительно изменяет значение, хранящееся в этой ячейке памяти. Просто эта модификация не получается создавать скачки данных по [1.10/21], потому что она атомарна.
- person Andy Prowl; 03.01.2013
const вы можете изменять переменную mutable, если вы делаете это атомарно (т.е. с правильной синхронизацией). Однако [17.6.5.9/3] говорит: [функция STL] не должна прямо или косвенно модифицировать [свои аргументы, (среди прочего) вызывая одну из своих const функций-членов]. Но поскольку эти const функции-члены могут изменять mutable переменные, это означает, что изменение mutable переменной не рассматривается как модификация согласно [17.6.5.9/3] до тех пор, пока вы делаете это атомарно. И я не могу найти, где это указано.
- person Andy Prowl; 03.01.2013
std::lock_guard внутри set_size, чтобы сделать функцию area потокобезопасной (т. е. соответствовать требованиям стандартной библиотеки)?
- person m7913d; 18.04.2021
area является потокобезопасным, даже если он обращается к памяти, которая может быть изменена set_size одновременно, что приводит к гонке данных? См. это связанный вопрос.
- person m7913d; 18.04.2021
Это дополнение к ответу K-ballo.
В этом контексте злоупотребляют термином потокобезопасный. Правильная формулировка: константная функция подразумевает потокобезопасную побитовую константу или внутреннюю синхронизацию, как указано в Херб Саттер (29:43) ) сам
Вызов константной функции из нескольких потоков одновременно должен быть потокобезопасным, без одновременного вызова неконстантной функции в другом потоке.
Таким образом, константная функция не должна (и не будет в большинстве случаев) действительно потокобезопасной, поскольку она может считывать память (без внутренней синхронизации), которая может быть изменена другой неконстантной функцией. В общем, это не потокобезопасно, поскольку гонка данных происходит, даже если только один поток записывает (а другой читает данные).
constозначает потокобезопасным. Это было бы чепухой, поскольку в противном случае это означало бы, что вы должны просто перейти вперед и пометить каждый потокобезопасный метод какconst. Скорее, вопрос, который мы действительно задаем,constПРЕДПОЛАГАЕТ потокобезопасность, и это то, о чем идет речь в данном обсуждении. - person user541686   schedule 30.07.2019