Does std::thread::join guarantee writes visibility

[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.