Является ли расслабленный атомный счетчик безопасным?
Гарантирует ли следующий код ожидаемое значение счетчика (40000000) в соответствии с моделью памяти C++11? (НЕ ограничено x86).
#include <atomic>
#include <thread>
using namespace std;
void ThreadProc(atomic<int>& counter)
{
for (int i = 0; i < 10000000; i++)
counter.fetch_add(1, memory_order_relaxed);
}
int main()
{
#define COUNT 4
atomic<int> counter = { 0 };
thread threads[COUNT] = {};
for (size_t i = 0; i < COUNT; i++)
threads[i] = thread(ThreadProc, ref(counter));
for (size_t i = 0; i < COUNT; i++)
threads[i].join();
printf("Counter: %i", counter.load(memory_order_relaxed));
return 0;
}
В частности, будут ли координаты ослабленной атомики так, что два потока не будут одновременно считывать текущее значение, независимо увеличивать его, и оба записывают свое увеличенное значение, фактически теряя одну из записей?
Некоторые строки из спецификации указывают на то, что в приведенном выше примере счетчик должен быть равным 40000000.
[Примечание: операции, определяющие memory_order_relaxed, ослаблены по отношению к упорядочению памяти. Реализации должны по-прежнему гарантировать, что любой данный атомарный доступ к определенному атомарному объекту будет неделим по отношению ко всем другим атомарным доступам к этому объекту. - конец примечания
,
Атомарные операции чтения-изменения-записи всегда должны читать последнее значение (в порядке изменения) записанной записи, связанной с операцией чтения-изменения-записи.
,
Все модификации конкретного атомарного объекта M происходят в некотором определенном общем порядке, называемом порядком модификации M. Если A и B являются модификациями атомного объекта M, а A происходит раньше (как определено ниже) B, то A предшествует B в порядок модификации M, который определен ниже.
Этот доклад также поддерживает идею о том, что приведенный выше код не распространяется на гонки. https://www.youtube.com/watch?v=KeLBd2EJLOU&feature=youtu.be&t=1h9m30s
Мне кажется, что существует неделимый порядок атомных операций, но у нас нет никаких гарантий, что это за порядок. Таким образом, все приращения должны выполняться "один перед другим" без гонки, которую я описал выше.
Но тогда несколько вещей потенциально указывают в другом направлении:
Реализации должны сделать атомные хранилища видимыми для атомных нагрузок в течение разумного периода времени.
Сотрудник сообщил мне, что в разговоре Саттера есть известные ошибки. Хотя я еще не нашел источников для этого.
Несколько членов сообщества C++ умнее, чем я предполагал, что расслабленное атомарное добавление может быть буферизовано так, чтобы последующее расслабленное атомарное добавление могло читать и оперировать устаревшим значением.
1 ответ
Код в вашем вопросе свободен от гонки; все приращения заказаны, и результат 40000000 гарантирован.
Ссылки в вашем вопросе содержат все соответствующие цитаты из стандарта.
Часть, в которой говорится, что атомные магазины должны быть видны в разумные сроки, относится только к отдельным магазинам.
В вашем случае счетчик увеличивается с помощью атомарной операции чтения-изменения-записи, и те гарантированно будут работать с последней в порядке модификации.
Множество членов сообщества C++ (...) подразумевают, что расслабленное атомарное добавление может быть буферизовано так, чтобы последующее расслабленное атомарное добавление могло читать и оперировать устаревшим значением.
Это невозможно, если модификации основаны на атомарных операциях чтения-изменения-записи.
Атомные приращения были бы бесполезны, если бы стандарт не гарантировал надежный результат