Рис. 5.2.Принудительное задание упорядочения неатомарных операций с помощью атомарных
Все это может показаться интуитивно очевидным — разумеется, операция записи значения происходит раньше операции его чтения! В случае атомарных операций по умолчанию это действительно так (на то и умолчания), однако подчеркну: у атомарных операций есть и другие возможности для задания требований к упорядочению, и скоро я о них расскажу.
Теперь, когда вы видели, как отношения происходит-раньше и синхронизируется-с работают на практике, имеет смысл поговорить о том, что же за ними стоит. Начнем с отношения синхронизируется-с.
5.3.1. Отношение синхронизируется-с
Отношение синхронизируется-с возможно только между операциями над атомарными типами. Операции над структурой данных (например, захват мьютекса) могут обеспечить это отношение, если в структуре имеются атомарные типы и определенные в ней операции выполняют необходимые атомарные операции. Однако реальным источником синхронизации всегда являются операции над атомарными типами.
Идея такова: подходящим образом помеченная атомарная операция записи W
над переменной x
синхронизируется-с подходящим образом помеченной атомарной операцией чтения над переменной x
, которая читает значение, сохраненное либо данной операцией записи ( W
), либо следующей за ней атомарной операцией записи над x
в том же потоке, который выполнил первоначальную операцию W,
либо последовательностью атомарных операций чтения-модификации-записи над x
(например, fetch_add()
или compare_exchange_weak()
) в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией W
(см. раздел 5.3.4).
Пока оставим в стороне слова «подходящим образом помеченная», потому что по умолчанию все операции над атомарными типами помечены подходящим образом. По существу сказанное выше означает ровно то, что вы ожидаете: если поток А сохраняет значение, а поток В читает это значение, то существует отношение синхронизируется-с между сохранением в потоке А и загрузкой в потоке В — как в листинге 5.2.
Уверен, вы догадались, что нюансы как раз и скрываются за словами «подходящим образом помеченная». Модель памяти в С++ допускает применение различных ограничений на упорядочение к операциям над атомарными типами, и именно это и называется пометкой. Варианты упорядочения доступа к памяти и их связь с отношением синхронизируется-с рассматриваются в разделе 5.3.3. А пока отступим на один шаг и поговорим об отношении происходит-раньше.
5.3.2. Отношение происходит-раньше
Отношение происходит-раньше — основной строительный блок механизма упорядочения операций в программе. Оно определяет, какие операции видят последствия других операций и каких именно. В однопоточной программе всё просто: если в последовательности выполняемых операций одна стоит раньше другой, то она и происходит-раньше. Иначе говоря, если операция А в исходном коде предшествует операции В, то А происходит-раньше В. Это мы видели в листинге 5.2: запись в переменную data
(3)происходит-раньше записи в переменную data_ready
(4). В общем случае между операциями, которые входят в состав одного предложения языка, нет отношения происходит-раньше, поскольку они не упорядочены. По-другому то же самое можно выразить, сказав, что порядок не определён. Мы знаем, что программа, приведённая в следующем листинге, напечатает " 1,2
" или " 2,1
", но что именно, неизвестно, потому что порядок двух обращений к get_num()
не определён.
Листинг 5.3.Порядок определения аргументов функции не определён
#include
void foo(int a, int b) {
std::cout << a << "," << b << std::endl;
}
int get_num() {
static int i = 0;
return ++i;
}
int main() {
foo(get_num(), get_num());←┐
Порядок обращений
} │
к get_num() не определен
Существуют случаи, когда порядок операций внутри одного предложения точно известен, например, если используется встроенный оператор «занятая» или результат одного выражения является аргументом другого выражения. Но в общем случае никакого отношения расположено-перед (а, значит, и отношения происходит-раньше) между ними не существует. Разумеется, все операции в одном предложении происходят раньше всех операций в следующем за ним предложении.
Читать дальше