9

Std::thread::join is said to 'synchronize-with' the joined thread, however synchronization doesnt tell anything about visibility of side effects, it merely governs the order of the visiblity, ie. in following example:

int g_i = 0;
int main()
{
  auto fn = [&] {g_i = 1;};
  std::thread t1(fn);
  t1.join();
  return g_i;
}

Do we have any guarantee in the c++ standard that this program will always return 1?

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    @CodyGray i dont understand closing the question, yes it may have been asked before, but the answers there were on the level of 'its okay, trust me'. What i wanted here is some C++ guru who can tell me why this works with some reference/authority – Salamander86 Jul 22 '20 at 10:24
  • 1
    You did not indicate that you had already read those other answers or that you had done any prior research. Therefore, I figured you hadn't seen them, and that they would answer your question. If you're looking for something more, you should state that explicitly in your question, not only to clearly demonstrate that it is not a duplicate, but also so that people can tailor their answers accordingly. – Cody Gray - on strike Jul 22 '20 at 10:28
  • @CodyGray OP may or may not have read the other posts. But there's sufficient description in the question that OP understood what "join" does. Besides, when "language lawyer" tag is used, typically more authoritative answers are sought. – P.P Jul 22 '20 at 10:31
  • @CodyGray Im sorry i thought that the **BOLD TEXT** asking for reference to C++ standard was enough. – Salamander86 Jul 22 '20 at 10:32
  • You can put anything in bold text; that doesn't prove that you've done any background research, that you understand the context, or even that you actually need a reference to the standard. Furthermore, if you're actually looking for authoritative references to the C++ language standard, you'll need to specify a *version* of the language, since each standard is different. If nothing else, they're going to have different section numbers, but they'll likely even have different contents. `std::thread::join` has been supported all the way back since C++11, so you looking for 11, 14, 17, ...? – Cody Gray - on strike Jul 22 '20 at 10:35
  • @CodyGray The "lack" specific version of C++ changes nothing in the question. You should read the C++ tag description which says **Unless the question explicitly mentions which version of the C++ standard is used, it is assumed that the current version is used.** You're clinging on to some inane technicalities when the question pretty clear (whether it can be answered suffciently is another thing). – P.P Jul 22 '20 at 10:44
  • 1
    Unable to answer a closed question, but the answer is yes. It is (vaguely) described in [memory_order](https://en.cppreference.com/w/cpp/atomic/memory_order). And while it seems like a description for atomics thru some circumstances (see next comment) it also applies to synchronized non-atomics. – user1810087 Jul 22 '20 at 12:19
  • 1
    From the [draft](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf) 1) §33.3.2.5-4`join()` - [...]Synchronization:The completion of the thread represented by *this *synchronizes with (6.8.2) the corresponding successful join()* return[...] 2) §6.8.2.1-(8.2) [...][Note: The relation “is dependency-ordered before” is analogous to “*synchronizes with*”, but uses release/-consume in place of release/acquire.— end note][...] 3) §6.8.2.1-9 [...]An evaluation A inter-thread happens before an evaluation B if[...] (9.1)A synchronizes with B, or (9.2)A is dependency-ordered before B[...] – user1810087 Jul 22 '20 at 12:19
  • 1
    @user1810087 It's been reopened if you'd like to give it a go. – Ted Lyngmo Jul 22 '20 at 17:37

2 Answers2

11

[thread.thread.member]:

void join();
Effects: Blocks until the thread represented by *this has completed.
Synchronization: The completion of the thread represented by *this synchronizes with the corresponding successful join() return.

Since the completion of the thread execution synchronizes with the return from thread::join, the completion of the thread inter-thread happens before the return:

An evaluation A inter-thread happens before an evaluation B if
A synchronizes with B

and thus happens before it:

An evaluation A happens before an evaluation B (or, equivalently, B happens after A) if:
A inter-thread happens before B

Due to (inter-thread) happens before transitivity (let me skip copypasting the whole definition of inter-thread happens before to show this), everything what happened before the completion of the thread, including the write of the value 1 into g_i, happens before the return from thread::join. The return from thread::join, in turn, happens before the read of value of g_i in return g_i; simply because the invocation of thread::join is sequenced before return g_i;. Again, using the transitivity, we establish that the write of 1 to g_i in the non-main thread happens before the read of g_i in return g_i; in the main thread.

Write of 1 into g_i is visible side effect with respect to the read of g_i in return g_i;:

A visible side effect A on a scalar object or bit-field M with respect to a value computation B of M satisfies the conditions:
A happens before B and
— there is no other side effect X to M such that A happens before X and X happens before B.
The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A.

Emphasis of the last sentence is mine and it guarantees that the value read from g_i in return g_i; will be 1.

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • OP has repeatedly said something along the lines: How does this guarantee that it makes "_all side-effects visible to all other threads?_" (even though "_all other threads_" wasn't actually mentioned in the question) - so perhaps a note about that there are only two threads that are synchronized in the `join()` will make it easier to accept the answer. – Ted Lyngmo Jul 22 '20 at 21:53
  • @TedLyngmo Yeah, I've seen that comment, but think I'll not add anything for now. – Language Lawyer Jul 22 '20 at 22:09
  • Certain library calls synchronize with other library calls performed by another thread. For example, an atomic store-release synchronizes with a load-acquire that takes its value from the store ([atomics.order]). [ Note: Except in the specified cases, reading a later value does not necessarily ensure visibility as described below. Such a requirement would sometimes interfere with efficient implementation.  — end note ] – Salamander86 Jul 23 '20 at 09:46
  • This is the definition of 'synchronizes with' from your reference, it seems to me they are explicitly saying that 'synchronizes with' does not ensure visibility. But following your logic it would seem that it always does. Is this not a contradiction? – Salamander86 Jul 23 '20 at 09:48
  • But apart from this weird note i accept your answer, it seems that 'synchronizes with' really does mean both 'happens-before' and 'is visible', thanks. – Salamander86 Jul 23 '20 at 10:12
  • 1
    @Salamander86 See [this answer](https://stackoverflow.com/a/50573557/) explaining what does the Note mean – Language Lawyer Jul 24 '20 at 21:08
  • 1
    Welcome in the 2k club. – peterh Aug 03 '20 at 09:58
  • would the changes be visible to all the threads or just to the thread that performed the join? – user14757101 Dec 31 '20 at 14:43
0

t1.join() will not return until thread execution is completed, so from your example g_i is guaranteed to be 1

rmflow
  • 4,445
  • 4
  • 27
  • 41
  • 1
    But this misses the question..how is thread completion guaranteed to make its writes visible to all other threads? – Salamander86 Jul 22 '20 at 10:04