Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to access a variable from the main thread after it was written to by another thread and after that thread was joined?

Tags:

c++

Is this thread-safe?

int x = 0;
std::thread([&]{ x = 1; }).join();
std::cout << x;

Variable x is accessed from two threads without using atomics or locks. However, the call to join() forces the accesses to x to be sequential.

Is a memory barrier required here?

like image 779
StackedCrooked Avatar asked Jul 07 '13 02:07

StackedCrooked


People also ask

Can two threads access the same data at the same time?

A data race is a state, in which at least two threads access shared data at the same time, and at least one of the threads is a writer. A critical section is a section of the code, which not more than one thread should access at any point in time.

What happens when two threads access same variable?

A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.

Is reading variables thread safe?

As long as it's a plain variable, it's no risk. If it is a property, reading it can possibly have side effects, so is not guaranteed to be thread safe. Save this answer.


1 Answers

Yes, that particular code snippet is thread safe; barriers or locks are not required.

This is the timeline of events with respect to your code:

thread 1 
--------
   |
  int x = 0;
  (write 0 to x)
   |
  std::thread                thread 2
  (start thread 2) --------> --------
   |                            |
  join();                      x = 1;
  (thread 1 suspended)         (write 1 to x)
   .                            |
   .                           thread 2 returns
   .                            |
  (thread 1 resumes) <-------   x
   |
  std::cout << x;
  (read from x)
   |
  thread 1 returns
   |
   x

As you can see, at no point is x being accessed by more than one thread. In fact, the use of join() effectively makes all accesses to x happen in sequential order, as you've surmised. The join() provides the synchronization in lieu of the synchronization you get from locks.

Basically, what you have is an example of how you can have multithreading with zero concurrency.

Of course, this is true only because of the call to join(), which happens immediately after you create the thread in the code snippet you provide. If you instead had something like this:

int x = 0;
std::thread t([&]{ x = 1; });
std::cout << x;
t.join(); // Move join() call here

The timeline instead may look like this:

thread 1 
--------
   |
  int x = 0;
  (write 0 to x)
   |
  std::thread                thread 2
  (start thread 2) --------> --------
   |                            |
  std::cout << x;              x = 1;
  (read from x)                (write 1 to x)    <-- PROBLEM!
   |                            |
  join();                       |
  (thread 1 suspended)          |
   .                            |
   .                           thread 2 returns
   .                            |
  (thread 1 resumes) <-------   x
   |
  thread 1 returns
   |
   x

Changing the order of join() in this manner will introduce a race.

like image 179
In silico Avatar answered Sep 20 '22 00:09

In silico