Is this the correct way to atomically read and wri

2019-02-24 12:42发布

问题:

A boolean flag is toggled by two threads. Does the following code make sense?

static bool ATOMIC_BOOL_READ( volatile bool& var )
{
    return __sync_fetch_and_or(&var, 0);
}

static void ATOMIC_BOOL_WRITE(volatile bool& var, bool newval )
{
    __sync_bool_compare_and_swap( &var, !newval, newval);
}

Note a few things:

  • I am passing in a bool reference. Make sense?

  • For kick's sake, I am also declaring it volatile.

  • The functions are static

Update:

The fundamental question I want to ask is: Whats the difference between atomicity and memory barrier? If Thread A is executing an atomic builtin on variable foo, then Thread B cannot do ANYTHING on variable foo; hence creating a memory barrier?

回答1:

Atomics are intrinsically non-portable and these are GCC extensions that might not exist anymore in the future and won't work on other compilers.

You should read the rest of the answer only once you understood completely the above statement.

A notable fact is that ALL machines existing have always guaranteed that access to data up to a certain size is atomic. This comes from the basic notion that data in memory and in the system bus is transfered with a certain granularity. A boolean should definitively be atomic in most machines, so:

bool ATOMIC_BOOL_READ(volatile bool* b) {
    bool v = *b;
    __sync_synchronize(); // ensure value pushed to memory
    return v;
}

void ATOMIC_BOOL_WRITE(volatile bool* b, bool v) {
    __sync_synchronize(); // read will return fresh value
   *b = v;
}

That is probably the reason why GCC does not provide simple load/store special atomic operations: they are already supposed to be atomic.



回答2:

You only need atomics for the read-modify-write family of actions. Isolated reads and writes are already atomic.

Your question says the two threads "toggle" the same bool. That is not what your posted functions do -- if you combine those functions to perform a toggle, it still isn't thread-safe.

Why not use std::atomic_int?

i=0; is thread-safe, i=i+1; isn't because if another thread does the same thing at the same time, i might end up being incremented only once instead of twice. This is a read-modify-write and an example problem sequence is (read1,read2,modify1,write1,modify2,write2) for threads 1 and 2. So far, so standard.

Now can you see why this also isn't thread-safe?

bool x = ATOMIC_BOOL_READ (&b);
x = !x;
ATOMIC_BOOL_WRITE (&b, x);

Your functions add no thread safety whatsoever. You could write a function

bool atomic_toggle_and_return_new_value (bool * b) { ... }

based on compare-and-swap or test-and-set, say. For more complicated cases where "both threads read and write to the same bool you need the readers and writers to cooperatively synchronise on some critical section (or look into lock-free and wait-free algorithms).



回答3:

__sync_bool_compare_and_swap is correct, but possibly much more expensive than necessary.

It depends what you need. __sync_lock_test_and_set will be cheaper (and guaranteeed to be atomic), but it does not report back if the operation was "successful" insofar as the value is what was expected (it's always "successful" anyway, and you do get the value back too, it just doesn't fail if it's not what you said). However, this is some information that is not always interesting.

Instead of the atomic builtins, you could use std::atomic<bool> if you compile in C++0x mode, which offers .load() and .store(). These functions are possibly more efficient (either exploiting knowledge that some operation is atomic anyway, or inserting barriers, or using special operations, or whatever), and your code is more portable (and more obvious).

Further, on pretty much every architecture, you can also expect (thought there is no guarantee!) a write to a bool being atomic anyway.

And... it really depends. If for example you just want to set a flag in one thread, and just want to see whether it's set in the other thread and it does not matter if it maybe takes a few more microseconds before this is realized, you can just assign the variable regardless of any atomicity.