8

What is the minimal framing required for x's type for this code to work, considering the implied synchronization when creating/joining a thread: std::atomic? volatile? nothing?

#include <thread>
#include <cassert>
int main() {
    int x = 123; // ***
    std::thread( [ & ] { assert( x == 123 ); x = 321; } ).join();
    assert( x == 321 );
    return 0;
}
vpozdyayev
  • 1,014
  • 5
  • 14
  • You'll want to read [this answer](http://stackoverflow.com/a/3114174/1769720) to a related question. – didierc Apr 16 '15 at 19:53
  • 2
    `std::thread( [ & ] { assert( x == 123 ); x = 321; } ).join();` There's no concurrent access to `x`, you could call the lambda sequentially for achieving the same behavior. `volatile` never serves for thread safety, BTW. – πάντα ῥεῖ Apr 16 '15 at 20:09

1 Answers1

14

The invocation of std::thread's constructor is synchronized and happens before the invocation of the copy of the thread function (30.3.1.2/6).

thread::join gives a similar synchronization guarantee: The completion of the thread happens before join returns (30.3.1.4/7).

Your code creates a thread and joins it immediately. Although your lambda captures by reference, there is no concurrency (the code runs as-if sequential), and the guarantees provided by std::thread make sure that you do not need any special framing to guard x. The assertion will never fail.

Assuming your code snippet was different so you actually have concurrent access of some kind, you would have to use std::atomic or a mutex. volatile would most definitively not be enough (except by coincidence).

Damon
  • 67,688
  • 20
  • 135
  • 185
  • I understand that by the spec `volatile` isn't sufficient because only the `std::atomic` types are declared to be data-race-free, but what is it about `int`, under the hood, that might make it racy? How do I recognize a failure? Is it because the four bytes of the int could be written in two 2-byte chunks on some platforms, whereas on other platforms that have native 32-bit ints it'll happen to just always work even though it's still wrong? On some platforms, could `std::atomic` be just an alias for `int`? Genuinely curious learner here. – antiduh Apr 16 '15 at 21:33
  • 1
    @antiduh It's not just racing on reading or writing that variable alone. Using atomics also constrains reordering. – T.C. Apr 16 '15 at 21:57
  • @antiduh: The atomic type and its specializations guarantee that you have certain (whichever you choose) ordering guarantees. That is, for example, a dependent write (or an independent write) is guaranteed to be globally visible before an atomic operation. No such guarantee is given by `volatile`. It might "work", but that's coincidence. About writing out integers, assuming mainstream CPUs and proper alignment, you are right. non-atomic `int` writes are not possible. However, this is not formally guaranteed and not portable, no defined ordering, and RMW ops (e.g. `++x`) are non-atomic too. – Damon Apr 17 '15 at 12:48