My understanding is that std::mutex lock and unlock have a acquire/release semantics which will prevent instructions between them from being moved outside.
So acquire/release should disable both compiler and CPU reorder instructions.
My question is that I take a look at GCC5.1 code base and don't see anything special in std::mutex::lock/unlock to prevent compiler reordering codes.
I find a potential answer in does-pthread-mutex-lock-have-happens-before-semantics which indicates a mail that says a external function call act as compiler memory fences.
Is it always true? And where is the standard?
std::mutex The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.
An operation has acquire semantics if other processors will always see its effect before any subsequent operation's effect. An operation has release semantics if other processors will see every preceding operation's effect before the effect of the operation itself.
A mutex is a lockable object that is designed to signal when critical sections of code need exclusive access, preventing other threads with the same protection from executing concurrently and access the same memory locations.
By design, std::mutex is not movable nor copyable. This means that a class A holding a mutex won't receive a default move constructor.
Threads are a fairly complicated, low-level feature. Historically, there was no standard C thread functionality, and instead it was done differently on different OS's. Today there is mainly the POSIX threads standard, which has been implemented in Linux and BSD, and now by extension OS X, and there are Windows threads, starting with Win32 and on. Potentially, there could be other systems besides these.
GCC doesn't directly contain a POSIX threads implementation, instead it may be a client of libpthread
on a linux system. When you build GCC from source, you have to configure and build separately a number of ancillary libraries, supporting things like big numbers and threads. That is the point at which you select how threading will be done. If you do it the standard way on linux, you will have an implementation of std::thread
in terms of pthreads.
On windows, starting with MSVC C++11 compliance, the MSVC devs implemented std::thread
in terms of the native windows threads interface.
It's the OS's job to ensure that the concurrency locks provided by their API actually works -- std::thread
is meant to be a cross-platform interface to such a primitive.
The situation may be more complicated for more exotic platforms / cross-compiling etc. For instance, in MinGW project (gcc for windows) -- historically, you have the option to build MinGW gcc using either a port of pthreads to windows, or using a native win32 based threading model. If you don't configure this when you build, you may end up with a C++11 compiler which doesn't support std::thread
or std::mutex
. See this question for more details. MinGW error: ‘thread’ is not a member of ‘std’
Now, to answer your question more directly. When a mutex is engaged, at the lowest level, this involves some call into libpthreads or some win32 API.
pthread_lock_mutex(); do_some_stuff(); pthread_unlock_mutex();
(The pthread_lock_mutex
and pthread_unlock_mutex
correspond to the implementations of lock
and unlock
of std::mutex
on your platform, and in idiomatic C++11 code, these are in turn called in the ctor
and dtor
of std::unique_lock
for instance if you are using that.)
Generally, the optimizer cannot reorder these unless it is sure that pthread_lock_mutex()
has no side-effects that can change the observable behavior of do_some_stuff()
.
To my knowledge, the mechanism the compiler has for doing this is ultimately the same as what it uses for estimating the potential side-effects of calls to any other external library.
If there is some resource
int resource;
which is in contention among various threads, it means that there is some function body
void compete_for_resource();
and a function pointer to this is at some earlier point passed to pthread_create...
in your program in order to initiate another thread. (This would presumably be in the implementation of the ctor
of std::thread
.) At this point, the compiler can see that any call into libpthread
can potentially call compete_for_resource
and touch any memory that that function touches. (From the compiler's point of view libpthread
is a black box -- it is some .dll
/ .so
and it can't make assumptions about what exactly it does.)
In particular, the call pthread_lock_mutex();
potentially has side-effects for resource
, so it cannot be re-ordered against do_some_stuff()
.
If you never actually spawn any other threads, then to my knowledge, do_some_stuff();
could be reordered outside of the mutex lock. Since, then libpthread
doesn't have any access to resource
, it's just a private variable in your source and isn't shared with the external library even indirectly, and the compiler can see that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With