2017-02-02 10 views
4

У меня ситуация с одним писателем, с несколькими читателями. Есть счетчик, на который пишет один поток, и любой поток может читать этот счетчик. Поскольку для одного потока записи не нужно беспокоиться о борьбе с другими потоками для доступа к данным, безопасен ли следующий код?Могу ли я читать атомную переменную без atom_load?

#include <stdatomic.h> 
#include <stdint.h> 

_Atomic uint32_t counter; 

// Only 1 thread calls this function. No other thread is allowed to. 
uint32_t increment_counter() { 
    atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); 
    return counter; // This is the line in question. 
} 

// Any thread may call this function. 
uint32_t load_counter() { 
    return atomic_load_explicit(&counter, memory_order_relaxed); 
} 

Автор Тема просто читает counter непосредственно без вызова каких-либо функций atomic_load*. Это должно быть безопасным (поскольку для нескольких потоков можно прочитать значение), но я не знаю, запрещает ли объявление переменной _Atomic использовать эту переменную или если вы всегда должны ее читать, используя один из atomic_load* функции.

+0

Рискну предположить, что строгий ответ: «Нет, это не безопасно», но у меня нет времени на самом деле исследовать это правильно Теперь. Попытка прямого чтения другой атомной переменной, которая может быть обновлена, предполагает, что само обновление является атомарным. Но если обновление переменной было атомарным, не было бы необходимости явно сделать его атомарным. –

ответ

1

Да, все операции, которые вы выполняете на объектах _Atomic, гарантируются, как если бы вы выдавали соответствующий вызов с последовательной согласованностью. И в вашем конкретном случае оценка эквивалентна atomic_load.

Но алгоритм, который используется там, является неправильным, потому что, делая atomic_fetch_add и оценку, возвращаемое значение уже может быть изменено другим потоком. Правильно было бы

uint32_t ret = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); 
return ret+1; 

Это выглядит немного неоптимальным, так как добавление делается в два раза, но хороший оптимизатор будет разобраться в этом.

+0

Заметьте, что я сказал, что это ситуация с одним писателем. Нет никакого риска для другого потока, изменяющего 'counter', потому что есть только один поток, который может сделать это в моем коде. Кроме того, есть ли у вас ссылка на стандарт, в котором говорится/подразумевается, что «все операции, которые вы делаете на объектах« _Atomic », гарантируются, как если бы вы выдавали соответствующий вызов с последовательной согласованностью»? Я пробовал читать со стандарта, но не мог найти нигде, где упоминалось, что можно использовать переменную '_Atomic' напрямую без одной из соответствующих атомных функций. – Cornstalks

+0

Немного разброса. В основном все арифметические операции имеют дополнительную фразу, которая гласит, что происходит, если объект в атоме квалифицирован. –

+0

А я думаю, что я нашел соответствующую цитату. «Нагрузки и хранилища объектов с атомными типами выполняются с помощью семантики' memory_order_seq_cst'. (раздел 6.2.6.1, пункт 9). Изучив стандарт, вы правы; это разбросанное (например, post-increment/decment talk about atomics в 6.5.2.4/2). Спасибо, что разъяснил это! – Cornstalks

0

Если переписать функцию, этот вопрос уходит:

uint32_t increment_counter() { 
    return 1 + atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); 
} 
+0

Да, это способ обойти эту проблему, и если бы это был мой настоящий код, это была бы хорошая идея. Но код, который я написал, был просто самым простым MVCE, который я мог бы написать, который все еще иллюстрировал реальный вопрос. В моем реальном коде две строки в функции «increment_counter» фактически делятся на две разные функции (хотя все еще с тем же ограничением, что используется только одним потоком). – Cornstalks