Implicit synchronization when creating/joining threads
Asked Answered
F

1

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;
}
Farceur answered 16/4, 2015 at 19:34 Comment(2)
You'll want to read this answer to a related question.Flop
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.Refund
U
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).

Unconventional answered 16/4, 2015 at 20:26 Comment(3)
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<int> be just an alias for int? Genuinely curious learner here.Deluge
@Deluge It's not just racing on reading or writing that variable alone. Using atomics also constrains reordering.Imputation
@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.Unconventional

© 2022 - 2024 — McMap. All rights reserved.