6

I have two threads sharing an uint64_t variable. The first thread just reads from the variable while the other thread just writes into. If I don't synchronize them using mutex/spinlock/atomic operations etc.., is there any possibility of reading another value from the writing thread wrote into? It is not important to read an old-value which was written by writing thread.

As an example, the writing thread increases the variable between 0 and 100, and the reading thread prints the value. So, is there any possibility to see a value in the screen different than [0-100] range. Currently I don't see any different value but I'm not sure it can cause a race condition.

Thanks in advance.

avatli
  • 610
  • 6
  • 16
  • Related (duplicate?): [Can num++ be atomic for 'int num'?](https://stackoverflow.com/questions/39393850/can-num-be-atomic-for-int-num) – Martin R Feb 01 '18 at 11:53
  • Even if you could prove that for this particular case it will always work, code like this is very fragile. Changing compiler or its settings or adding code might break it. Is taking a shortcut here worth the potential problems in the future? Race conditions can be very hard to debug. – user694733 Feb 01 '18 at 12:04
  • @MartinR, I'm not interested in any atomic or synchronization for my case. I'm just curious to know what happens in my case. – avatli Feb 02 '18 at 06:15

2 Answers2

4

On a 64 bit processor, the data transfers are 64 bits at a time, so you will see logically consistent values i.e. you won't see 32 bits from before the write and 32 bits after the write. This is obviously not true of 32 bit processors.

The kind of issues you will see are things like, if the two threads are running on different cores, the reading thread will not see changes made by the writing thread until the writing thread's core flushes its cache. Also, optimisation may make either thread not bother to read memory at all in the loop. For example, if you have:

uint64_t x = 0;

void increment()
{
    for (int i = 0 ; i < 100 ; ++i)
    {
        x++;
    }
}

It is possible that the compiler will generate code that reads x into a register at the start of the loop and not write it back to memory until the loop exits. You need things like volatile and memory barriers.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • 2
    In addition, even if the CPU is 64 bits and can access the variable in one go, that doesn't necessarily mean that the access is atomic. Thread 1 can read the variable into a register, thread 2 comes in and writes another value to the variable, thread 1 makes a decision based on the old value. Depending on the spec, this might not matter at all, or it might be a fatal bug. For example code such as `if(x == 1) print(x)` could print the value 2. – Lundin Feb 01 '18 at 14:14
  • 1
    What about misaligned accesses, along with accesses that also cross a page boundary? Two different adjacent virtual pages can map to wildly different physical pages. – Andrew Henle Feb 01 '18 at 15:07
  • 2
    @AndrewHenle I would have thought the compiler would allocate `uint64_t` on aligned boundaries for performance reasons, but I could be wrong. – JeremyP Feb 01 '18 at 16:30
  • @andrew-henle I'm not sure it is possible that a compiler maps an uint64_t variable in two different virtual pages. – avatli Feb 02 '18 at 06:21
  • @JeremyP *I would have thought the compiler would allocate uint64_t on aligned boundaries for performance reasons, but I could be wrong.* x86 is so forgiving of misaligned access that there's now a whole population of C programmers who don't understand things like aliasing violations and freely use things like `#pragma packed ...` without understanding the implications. Bugs like this are rampant: https://bugs.python.org/issue28055 Just Google "sigbus on sparc"... – Andrew Henle Feb 02 '18 at 11:49
  • @AndrewHenle Misalignment in C has nothing to do with atomicity. C requires everything to be properly aligned. Any structure packing is a compiler extension and depending on architecture it might break even non-multithreaded code. –  Feb 11 '18 at 14:52
  • @Ivan *Misalignment in C has nothing to do with atomicity.* Not explicitly, but aligned accesses are likely to be implemented atomically by the underlying hardware. *C requires everything to be properly aligned.* I'd say that the C standard doesn't really address alignment of variables directly at all. *Any structure packing is a compiler extension and depending on architecture it might break even non-multithreaded code.* So we agree? – Andrew Henle Feb 11 '18 at 17:26
  • @AndrewHenle My point was that if you do not align data properly, then your code will fail and even `_Atomic` won't help. –  Feb 11 '18 at 17:28
3

All bad things can happen if you have a race condition on such a variable.

The correct tool with modern C for this are atomics. Just declare your variable

uint64_t _Atomic counter;

Then, all your operations (load, store, increment...) will be atomic, that is indivisible, uninterruptible and linearizable. No mutex or other protection mechanism is necessary.

This has been introduced in C11, and recent C compilers e.g gcc and clang, now support this out of the box.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177