6

Since I've started multi-threading, I've been asking myself this one question :

Is writing and reading a variable from different threads undefined behavior?

Let's use the minimal example where we increment an integer in a thread and read the integer inside another one.

void thread1()
{
    x++;
}

void thread2()
{
    if (x == 5)
    {
        //doSomething
    }
}

I understand that the addition operation is not atomic and therefore I could make a read from the second thread while the first thread is in the middle of the adding operation, but there is something i'm not quite sure of.

Does x keeps his value until the whole addition operation is completed and then is assigned this new value, or does x have an intermediate state where reading from it would result in undefined behavior.

If the first theory applies, then reading from x while it's being writing to would simply return the value before the addition and wouldn't be so problematic.

If the second theory is true, could someone explain more in detail what is the process of the addition operation and why it would be undefined behavior (maybe with an example?)

Thanks

  • 1
    You should read [this](http://preshing.com/20130618/atomic-vs-non-atomic-operations/) – WhiZTiM Jul 08 '16 at 20:35
  • It is UB. There is no guarantee that thread 2 will ever see I change in the value of `x`. Even worse the compiler may reason that `x` never changes and remove the `if` test. Read about `std::atomic<*>`. – Richard Critten Jul 08 '16 at 20:45
  • Just for the curiosity: how come compiler know it runs on different thread and remove the if? – Aik Jul 08 '16 at 20:52
  • @Aik The thing is the compiler does not know about the other thread (that is what memory fences/std::atomic is for). When optimising if the compiler can prove that during the single thread of execution `x` does not change from it's initial value it can remove the test. It can even remove `x++` if it can prove that no code on the same thread can observe the change. – Richard Critten Jul 08 '16 at 21:02
  • The answer to your question depends on at least the type of x and the CPU you are running on. If you are running on a processor that must write to memory twice to perform the add, then thread 2 can read x between these writes. UB. From the c++ execution model, you are missing a sychronizes-with between the write and read. – David Thomas Jul 08 '16 at 21:19
  • @whiztim thanks for the great link – Just some dude Jul 11 '16 at 13:19

2 Answers2

0

The comments already got the basics right.

The compiler, when compiling a single function may consider the ways in which a variable is changed. If the function cannot directly or indirectly change a certain variable, then the compiler may assume that there is no change to that variable whatsoever, unless there's thread synchronization. In that case the compiler must deal with the possibility of another thread changing those variables.

If the compiler assumption is violated (i.e. you have a bug), then literally anything may happen. This is not constrained, because that would severely restrict optimizers. You may make some assumptions that x has some unique address in memory, but optimizers are known to move variables around and have multiple variables share a single address (just at different times). Such optimizations may very well be justified based on a single-thread assumption, one that your example is violating. Your second thread may think it's looking at x, but it might also be getting y.

MSalters
  • 173,980
  • 10
  • 155
  • 350
-3

x (32bit variable) will be always defined on 32+bits cpu however not so precisely. You know that x can be any value from start up to end range defined by ++.

like in following case: x is initialized to 0 and you call 5 times thread1 the thread 2 can see this x in range from 0 to 5.

It means I can consider assignment of integer to memory as atomic.

There are some reasons why x on both thread is not synchronized e.g. while x on thread1 is 5 on the thread2 can be 0 in the same time. One of the reason is cpu cache which is different for each core. To synchronise the value between caches you have to use memory barriers. You can use for example std::atomic which do a great job for you

Aik
  • 3,528
  • 3
  • 19
  • 20
  • 1
    Wrong on a worrying number of counts. `x` isn't necessarily 32 bits, and its width doesn't depend on some fuzzy "CPU width". `x` does have a range, but that's not defined by `++`. You can't consider `x` atomic, because it's type is just `int` and not `atomic`. There's a reason we have the latter type! And without synchronization, the other thread can see any value, not just 0 to 5 but also 6, -1 and purple. (yes! There are no restrictions whatsoever on unsynchronized access) – MSalters Jul 08 '16 at 23:34