Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How 'undefined' a race condition can be?

Let's say I define a following C++ object:

class AClass
{
public:
    AClass() : foo(0) {}
    uint32_t getFoo() { return foo; }
    void changeFoo() { foo = 5; }
private:
    uint32_t foo;
} aObject;

The object is shared by two threads, T1 and T2. T1 is constantly calling getFoo() in a loop to obtain a number (which will be always 0 if changeFoo() was not called before). At some point, T2 calls changeFoo() to change it (without any thread synchronization).

Is there any practical chance that the values ever obtained by T1 will be different than 0 or 5 with modern computer architectures and compilers? All the assembler code I investigated so far was using 32-bit memory reads and writes, which seems to save the integrity of the operation.

What about other primitive types?

Practical means that you can give an example of an existing architecture or a standard-compliant compiler where this (or a similar situation with a different code) is theoretically possible. I leave the word modern a bit subjective.


Edit: I can see many people noticing that I should not expect 5 to be read ever. That is perfectly fine to me and I did not say I do (though thanks for pointing this aspect out). My question was more about what kind of data integrity violation can happen with the above code.

like image 775
Andrew Avatar asked Feb 22 '13 08:02

Andrew


3 Answers

In practice, you will not see anything else than 0 or 5 as far as I know (maybe some weird 16 bits architecture with 32 bits int where this is not the case).

However whether you actually see 5 at all is not guaranteed.

Suppose I am the compiler.

I see:

while (aObject.getFoo() == 0) {
    printf("Sleeping");
    sleep(1);
}

I know that:

  • printf cannot change aObject
  • sleep cannot change aObject
  • getFoo does not change aObject (thanks inline definition)

And therefore I can safely transform the code:

while (true) {
    printf("Sleeping");
    sleep(1);
}

Because there is no-one else accessing aObject during this loop, according to the C++ Standard.

That is what undefined behavior means: blown up expectations.

like image 169
Matthieu M. Avatar answered Nov 17 '22 09:11

Matthieu M.


In practice, all mainstream 32-bit architectures perform 32-bit reads and writes atomically. You'll never see anything other than 0 or 5.

like image 30
Marcelo Cantos Avatar answered Nov 17 '22 08:11

Marcelo Cantos


Not sure what you're looking for. On most modern architectures, there is a very distinct possibility that getFoo() always returns 0, even after changeFoo has been called. With just about any decent compiler, it's almost guaranteed that getFoo(), will always return the same value, regardless of any calls to changeFoo, if it is called in a tight loop.

Of course, in any real program, there will be other reads and writes, which will be totally unsynchronized with regards to the changes in foo.

And finally, there are 16 bit processors, and there may also be a possibility with some compilers that the uint32_t isn't aligned, so that the accesses won't be atomic. (Of course, you're only changing bits in one of the bytes, so this might not be an issue.)

like image 2
James Kanze Avatar answered Nov 17 '22 10:11

James Kanze