Упорядочение захват-освобождение — шаг от ослабленного упорядочения в сторону большего порядка; полной упорядоченности операций еще нет, но какая-то синхронизация уже возможна. При такой модели атомарные операции загрузки являются операциями захвата (memory_order_acquire), атомарные операции сохранения — операциями освобождения (memory_order_release), а атомарные операции чтения-модификации-записи (например, fetch_add() или exchange()) — операциями захвата, освобождения или того и другого (memory_order_acq_rel). Синхронизация попарная — между потоком, выполнившим захват, и потоком, выполнившим освобождение. Операция освобождения синхронизируется-с операцией захвата, которая читает записанное значение. Это означает, что различные потоки могут видеть операции в разном порядке, но возможны все-таки не любые порядки. В следующем листинге показала программа из листинга 5.4, переработанная под семантику захвата-освобождения вместо семантики последовательной согласованности.

Листинг 5.7. Из семантики захвата-освобождения не вытекает полная упорядоченность

#include

#include

#include

std::atomic x, y;

std::atomic z;

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 x, y;

std::atomic z;

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)

}

Перейти на страницу:

Похожие книги