Операции над атомарными типами, выполняемые в режиме ослабленного упорядочения, не участвуют в отношениях синхронизируется-с. Операции над одной и той же переменной в одном потоке по-прежнему связаны отношением происходит-раньше, но на относительный порядок операций в разных потоках не накладывается почти никаких ограничений. Есть лишь одно требование: операции доступа к одной атомарной переменной в одном и том же потоке нельзя переупорядочивать — если данный поток видел определенное значение атомарной переменной, то последующая операция чтения не может извлечь предыдущее значение этой переменной. В отсутствие дополнительной синхронизации порядок модификации отдельных переменных — это единственное, что объединяет потоки, использующие модель memory_order_relaxed.

Чтобы продемонстрировать, до какой степени могут быть «ослаблены» операции в этой модели, достаточно всего двух потоков (см. листинг 5.5).

Листинг 5.5. К ослабленным операциям предъявляются очень слабые требования

#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_relaxed); ←(2)

}

void read_y_then_x() {

 while (!y.load(std::memory_order_relaxed));←(3)

 if (x.load(std::memory_order_relaxed))     ←(4)

  ++z;

}

int main() {

 x = false;

 y = false;

 z = 0;

 std::thread а(write_x_then_y);

 std::thread b(read_y_then_x);

 a.join();

 b.join();

 assert (z.load() != 0); ←(5)

}

На этот раз утверждение (5) может сработать, потому что операция загрузки x (4) может прочитать false, даже если загрузка y (3) прочитает true, а сохранение x (1) происходит-раньше сохранения y (2). x и y — разные переменные, поэтому нет никаких гарантий относительно порядка видимости результатов операций над каждой из них.

Ослабленные операции над разными переменными можно как угодно переупорядочивать при условии, что они подчиняются ограничивающим отношениям происходит-раньше (например, действующим внутри одного потока). Никаких отношений синхронизируется-с не возникает. Отношения происходит-раньше, имеющиеся в листинге 5.5, изображены на рис. 5.4, вместе с возможным результатом. Несмотря на то, что существует отношение происходит-раньше отдельно между операциями сохранения и операциями загрузки, не существует ни одного такого отношения между любым сохранением и любой загрузкой, поэтому операция загрузки может увидеть операции сохранения не в том порядке, в котором они происходили.

Рис. 5.4. Ослабленные атомарные операции и отношения происходит-раньше

Рассмотрим чуть более сложный пример с тремя переменными и пятью потоками.

Листинг 5.6. Ослабленные операции в нескольких потоках

#include

#include

#include

std::atomic x(0), y(0), z(0);←(1)

std::atomic go(false);      ←(2)

unsigned const loop_count = 10;

struct read_values {

 int x, y, z;

};

read_values values1[loop_count];

read_values values2[loop_count];

read_values values3[loop_count];

read_values values4[loop_count];

read_values values5[loop_count];

void increment(

 std::atomic* var_to_inc, read_values* values) {

 while (!go) ←(3) В цикле ждем сигнала

  std::this_thread::yield();

 for (unsigned i = 0; i < loop_count; ++i) {

  values[i].x = x.load(std::memory_order_relaxed);

  values[i].y = y.load(std::memory_order_relaxed);

  values[i].z = z.load(std::memory_order_relaxed);

  var_to_inc->store(i + 1, std::memory_order_relaxed);←(4)

  std::this_thread::yield();

 }

}

void read_vals(read_values* values) {

 while (!go) ←(5) В цикле ждем сигнала

 std::this_thread::yield();

 for (unsigned i = 0; i < loop_count; ++i) {

  values[i].x = x.load(std::memory_order_relaxed);

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

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