Упорядочение захват-освобождение — шаг от ослабленного упорядочения в сторону большего порядка; полной упорядоченности операций еще нет, но какая-то синхронизация уже возможна. При такой модели атомарные операции загрузки являются операциями memory_order_acquire), атомарные операции сохранения — операциями memory_order_release), а атомарные операции чтения-модификации-записи (например, fetch_add() или exchange()) — операциями memory_order_acq_rel). Синхронизация попарная — между потоком, выполнившим захват, и потоком, выполнившим освобождение.
Листинг 5.7. Из семантики захвата-освобождения не вытекает полная упорядоченность
#include
#include
#include
std::atomic
std::atomic
void write_x() {
x.store(true, std::memory_order_release);
}
void write_y() {
y.store(true, std::memory_order_release);
}
void read_x_then_y() {
while (!x.load(std::memory_order_acquire));
if (y.load(std::memory_order_acquire)) ←(1)
++z;
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));
if (x.load(std::memory_order_acquire)) ←(2)
++z;
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x);
std::thread b(write_y);
std::thread с(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load() != 0); ←(3)
}
В данном случае утверждение (3) x (2) и y (1) могут прочитать значение false. Запись в переменные x и y производится из разных потоков, но упорядоченность между освобождением и захватом в одном потоке никак не отражается на операциях в других потоках.
На рис. 5.6 показаны отношения происходит-раньше, имеющие место в программе из листинга 5.7, а также возможный исход, когда два потока-читателя имеют разное представление о мире. Это возможно, потому что, как уже было сказано, не существует отношения происходит-раньше, которое вводило бы упорядочение.
Рис. 5.6. Захват-освобождение и отношения происходит-раньше
Чтобы осознать преимущества упорядочения захват-освобождение, нужно рассмотреть две операции сохранения в одном потоке, как в листинге 5.5. Если при сохранении y задать семантику memory_order_release, а при загрузке y — семантику memory_order_acquire, как в листинге ниже, то операции над x станут упорядоченными.
Листинг 5.8. Операции с семантикой захвата-освобождения могут упорядочить ослабленные операции
#include
#include
#include
std::atomic
std::atomic
void write_x_then_y() {
x.store(true,std::memory_order_relaxed); ←(1)
y.store(true,std::memory_order_release); ←(2)
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));←(3)
if (x.load(std::memory_order_relaxed)) ←(4)
++z;
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load() != 0); ←(5)
}