Пример неправильного использования std::memory_order:: Relaxed в стандарте C++ [алгоритмы.parallel.exec / 5 в n4713]

Один из примеров неправильного использования std::memory_order::relaxed в стандарте C++:

std::atomic<int> x{0};
int a[] = {1,2};
std::for_each(std::execution::par, std::begin(a), std::end(a), [&](int) {
    x.fetch_add(1, std::memory_order::relaxed);
    // spin wait for another iteration to change the value of x
    while (x.load(std::memory_order::relaxed) == 1) { } // incorrect: assumes execution order
});

И тогда он говорит:

Приведенный выше пример зависит от порядка выполнения итераций и не завершится, если обе итерации выполняются последовательно в одном и том же потоке выполнения.

Вопросы:

  1. В комментарии говорится: "неверно: предполагается порядок выполнения". Что такое "предполагаемый порядок исполнения"? Я скучаю по нему.

  2. Что означает "итерация" в "Приведенном выше примере зависит от порядка выполнения итераций"? Означает ли это итерацию в цикле while? Или это относится к итерацииstd::for_each?

  3. Если итерации std::for_eachвыполняются параллельно разными потоками, правда ли, что одна из итераций / потоков не завершится? Потому какx.fetch_add(1, std::memory_order::relaxed) является атомарным, поэтому один поток сделает x 1 и еще один сделают x2, и невозможно иметь x == 1 для обоих потоков. Нет?

1 ответ

Решение

"неверно: предполагается порядок исполнения". Что такое "предполагаемый порядок исполнения"?

Предполагается, что тело лямбды выполняется несколькими потоками, а не одним. Стандарт скорее говорит, что он может выполняться параллельно.

На что ссылаются "итерации" в "Приведенном выше примере зависит от порядка выполнения итераций"?

Вероятно, это относится к выполнению лямбды другим потоком. Но стандарт не гарантирует наличия другого потока. См. Execution_policy_tag_t:

parallel_policyТип политики выполнения, используемый как уникальный тип для устранения неоднозначности перегрузки параллельного алгоритма и индикации того, что выполнение параллельного алгоритма может быть распараллелено. Вызовы функций доступа к элементам в параллельных алгоритмах, вызываемых с помощью этой политики (обычно указываемой как std::execution::par), могут выполняться либо в вызывающем потоке, либо в потоке, неявно созданном библиотекой для поддержки выполнения параллельного алгоритма. Любые такие вызовы, выполняемые в одном потоке, имеют неопределенную последовательность по отношению друг к другу.

Другие вопросы по тегам