Can I perform arithmetic operations on an atomic variable directly?
Since I find the C standard library provides a lot of utility functions like atomic_fetch_add
to perform the addition between an atomic variable and a non-atomic variable. But, I am curious since the variable is atomic, can I have arithmetic operations directly on it? Like in the code shown below:
#include <threads.h>
#include <stdio.h>
#include <stdatomic.h>
atomic_int i = 0;
int run(void* v) {
i = 100; // <- is this operaiton thread-safe?
// atomic_fetch_add(&i, 100);
printf("%d\n", i);
return thrd_success;
}
int main(void) {
thrd_t thread;
thrd_create(&thread, run, NULL);
thrd_join(thread, NULL);
return 0;
}
CodePudding user response:
Compound assignments to variables with atomic types are explicitly allowed in C2011: quoting N1570 §6.15.6.2p3
A compound assignment of the form
E1 op= E2
is equivalent to the simple assignment expressionE1 = E1 op (E2)
, except that the lvalueE1
is evaluated only once, and with respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. IfE1
has an atomic type, compound assignment is a read-modify-write operation withmemory_order_seq_cst
memory order semantics. [footnote 113]
Emphasis mine. Footnote 113 goes on to give a specific example of how E1 op= E2
can be translated into <stdatomic.h>
primitive operations when E1 is an atomic type.
The other operators that I'm confident one may apply to atomic types are postfix
and --
, which are also guaranteed to perform an atomic read-modify-write (§6.5.2.4), and simple assignment, which is guaranteed to perform an atomic load or store as appropriate (§6.2.6.1).
Caution: Prefix
and --
are not guaranteed to perform an atomic read-modify-write (compare 6.5.2.4 with 6.5.3.1).
I read this sentence of 6.2.6.1
Loads and stores of objects with atomic types are done with
memory_order_seq_cst
semantics.
as implying that is valid to use an atomic lvalue as an operand to most other operators, and an atomic load is performed, after which the value is not treated specially. Don't quote me on this part.
C may have different rules. I will leave exegesis of the C standard to someone else.
CodePudding user response:
The i = 10;
statement maintains the atomicity of i
, because it is used as an lvalue expression. From cppreference (bolding mine):
Built-in increment and decrement operators and compound assignment are read-modify-write atomic operations with total sequentially consistent ordering (as if using memory_order_seq_cst). If less strict synchronization semantics are desired, the standard library functions may be used instead.
The example given on the linked page uses the built-in (pre-)increment operation on the acnt
variable, but it could just as well have used a compound assignment, as your code does.
However, more complex arithmetic operations may cause the i
variable to lose its atomicity, if it is not used strictly as an lvalue expression. From the same page:
Atomic properties are only meaningful for lvalue expressions. Lvalue-to-rvalue conversion (which models a memory read from an atomic location to a CPU register) strips atomicity along with other qualifiers.