views:

105

answers:

2

Here some C++ code that is accessed from multiple threads in parallel. It has a critical section:

lock.Acquire();
current_id = shared_id;
// small amounts of other code
shared_id = (shared_id + 1) % max_id;
lock.Release();
// do something with current_id

The class of the lock variable is wrapper around the POSIX mutex implementation. Because of the module operations, it is not possible to use atomic operations.

Is it possible that a gcc compiler with a O3 flag optimizes the code so that the assignment of current_id is moved before the lock?

+1  A: 

Normally the compiler should not make such harmful optimizations. If you're still unsure, you could use the volatile keyword to prevent optimizations on that id variables.

Kosi2801
volatile doesn't prevent all relevant optimizations. It prevents the value from being cached in a register, but pretty much nothing else. Reads/writes can still be reordered.
jalf
A good treatment of when to use volatile, as part of the Linux kernel documentation: http://www.mjmwired.net/kernel/Documentation/volatile-considered-harmful.txt
asveikau
@jalf: but do compilers really reorder reads/writes so that they cross function-call-boundaries? Assignments and function calls mixed together are quite common and I guess there would be a lot of problems all over the place if the assigment of variables could switch order with function calls.
Kosi2801
@Kosi2801: A compiler might do that (imagine if it inlines the function call), but only if it determines that it is safe to do so. But the compiler doesn't, and can't, take threading into account when performing that analysis. It preserves the semantics within one thread of execution, but it can and will reorder reads/writes that may affect other threads. And even if the compiler doesn't, the CPU itself does the same thing. The *only* way to ensure ordering of reads/writes is with a memory barrier. Both the compiler and CPU respects those.
jalf
Thanks for the more in-depth explanation, it's more clear now for me :)
Kosi2801
+4  A: 

It is possible to compile with O3!

The compiler will never optimize across a function call unless the function is marked as pure using function-attributes.

The mutex functions aren't pure, so it's absolutely safe to use them with O3.

Nils Pipenbrinck
You mean "unless the function is marked as pure **or the compiler is able to determine that it is safe to do so**. Of course, the end result is the same, the compiler won't make an optimization in general, unless it can verify that it is safe.
jalf
If `current_id` and `shared_id` are both local variables which have not escaped the current scope (address hasn't been given to anybody else, etc.) then the optimizer could well reorder those line disregarding possible mutation from the external function calls. I assume that's not the case here, though.
ephemient
@ephemient: but if they are local and no one on the outside knows of them how could an external function call modify them?
Kosi2801
@Kosi801: Perhaps through buffer overruns of some other data on the stack or other illegal memory accesses. It's unportable and wrong, but I've seen people try `int a, b; foo(
ephemient