1

I have an array of structs that's a global variable and one of the struct members is a boolean. One thread in a particular CPU is reading the boolean value, and another thread in a different CPU can periodically change the boolean value by assigning it true or false (my_array[index].bool = true/false).

I understand that not using any form of synchronization mechanism will lead to undefined behavior.

However, assuming that:

  1. in my implementation, sizeof(bool)==1,
  2. and from this previous post, setting a one byte boolean variable to true/false will be an atomic operation on an x86_64 bit system,
  3. and this answer paragraph 3 suggests that the MESIF protocol will keep the caches of multiple cores coherent (including the L1 private caches of different cores)
  4. my code will only ever run on an 64-bit x86 architecture with gcc 4.8.4 compiler optimization flag O3

Can I say with certainty that under the above-mentioned conditions, I can get away without using synchronization mechanisms like mutex locks, etc and not have undefined behavior?

Community
  • 1
  • 1
user2635088
  • 1,598
  • 1
  • 24
  • 43
  • Is there a specific reason you cannot use std::atomic here? The compiler should do whatever is smartest (as in as fast as possible while remaining safe) on the current architecture. –  Jan 03 '17 at 17:04
  • One issue I see here is that the code can be reordered and that can/will cause different behavior then when you use actual synchronization to mark the code non-reoderable. – NathanOliver Jan 03 '17 at 17:06
  • @Frank mostly its a theoretical question, just for my edification. Simply replacing my current bool declaration with std::atomic within my struct will guarantee defined behavior, at no performance cost? (I understand correctness is more important than performance most of the times, but this is purely for theoretical understanding purposes) – user2635088 Jan 03 '17 at 17:28
  • If you have figured out a way of implementing the equivalent of `std::atomic` without any cost, don't you think your compiler writer has done that too? – Bo Persson Jan 03 '17 at 17:30
  • @NathanOliver could you please explain a bit more? – user2635088 Jan 03 '17 at 17:32
  • 1
    @user2635088 Under the `as-is` rule the compiler can move code around to make it more optimized as long as you get the same effect. With multithreading this can cause some unintended consequences thus support was added to the language to stop the compiler from doing that. – NathanOliver Jan 03 '17 at 17:36
  • @NathanOliver so if I use `std::atomic` instead of `bool` and the only two operations that I'm performing on my bool are `(if my_array[index].bool && some_other_condition)` and `my_array[index].bool = true/false`, exactly which instructions are being prevented from being reordered? – user2635088 Jan 03 '17 at 17:46
  • 3
    As a specific example: `vec->push_back(2); updated = true;` There is nothing stopping the compiler from setting `updated` to `true` before pushing 2 in the vector. This is not purely theoretical either, compilers will do this all the time to minimize pipeline stalls. –  Jan 03 '17 at 17:48
  • 1
    If you used a `std::atomic` then the code after an update cannot be reordered up above it and code above it cannot be reordered below it. – NathanOliver Jan 03 '17 at 17:49

1 Answers1

5

No, you can't say that with certainty because no standard provides that guarantee. When you "synthesize" a guarantee by combining information from lots of places and ultimately rely on your inability to think of any way it can fail, you do not have certainty.

There are lots of examples of people who thought they had certainty in this way and then things failed in ways they could not think of. That said, I can't think of any way this could fail either, but I wouldn't rely on it.

Note that you should not expect memory operations to provide any ordering, just that a change in one thread will eventually be visible in another. In particular, you cannot assume that a thread that sees a change to a particular boolean will see any memory operations that appear prior to that one in the code. Compilers and CPUs are free to, and do in practice, reorder memory operations.

So even if it was guaranteed, you couldn't use it for much. Even using it for a boolean to shut a thread down like while (!shutdown) do_work(); in one thread and shutdown = true; in another is risky. If the compiler can prove that do_work() cannot modify shutdown, it can optimize out the check of shutdown and the loop may not ever terminate.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278