Home > OS >  Can I perform arithmetic operations on an atomic variable directly?
Can I perform arithmetic operations on an atomic variable directly?

Time:01-03

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 expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once, and with respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. If E1 has an atomic type, compound assignment is a read-modify-write operation with memory_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.

  • Related