I’ve rarely thought about what happens between two consecutive expressions, between the call to a function and the execution of its body's first expression, or between a call to a constructor and the execution of its initializer. Then I started reading about concurrency...
1.) In two consecutive calls to std::thread
’s constructor with the same callable (e.g. function, functor, lambda), whose body begins with a std::lock_guard
initialization with the same std::mutex
object, does the standard guarantee the thread corresponding to the first thread
constructor call executes the lock-protected code first?
2.) If the standard doesn’t make the guarantee, then is there any theoretical or practical possibility the thread corresponding to the second thread
constructor call executes the protected code first? (e.g. heavy system load during the execution of the initializer or body of the first thread
constructor call)
Here’s a global std::mutex
object m
and a global unsigned
num
initialized to 1
. There is nothing but whitespace between function foo
’s body’s opening brace {
and the std::lock_guard
. In main
, there are two std::thread
s t1
and t2
. t1
calls the thread constructor first. t2
calls the thread constructor second. Each thread is constructed with a pointer to foo
. t1
calls foo
with unsigned
argument 1
. t2
calls foo
with unsigned
argument 2
. Depending on which thread locks the mutex
first, num
’s value will be either a 4
or a 3
after both threads have executed the lock-protected code. num
will equal 4
if t1
beats t2
to the lock. Otherwise, num
will equal 3
. I ran 100,000 trials of this by looping and resetting num
to 1
at the end of each loop. (As far as I know, the results don’t and shouldn’t depend on which thread is join()
ed first.)
#include <thread>
#include <mutex>
#include <iostream>
std::mutex m;
unsigned short num = 1;
void foo(unsigned short par) {
std::lock_guard<std::mutex> guard(m);
if (1 == num)
num += par;
else
num *= par;
}
int main() {
unsigned count = 0;
for (unsigned i = 0; i < 100000; ++i) {
std::thread t1(foo, 1);
std::thread t2(foo, 2);
t1.join();
t2.join();
if (4 == num) {
++count;
}
num = 1;
}
std::cout << count << std::endl;
}
In the end, count
equals 100000
, so it turns out t1
wins the race every time. But these trials don’t prove anything.
3.) Does the standard mandate “first to call thread
constructor” always implies “first to call the callable passed to the thread
constructor”?
4.) Does the standard mandate “first to call the callable passed to the thread
constructor” always implies “first to lock the mutex
”; provided that within the callable’s body, there exists no code dependent upon the parameter(s) passed to the callable prior to the line with the std::lock_guard
initialization? (Also rule out any callable’s local static
variable, like a counter of number of times called, which can be used to intentionally delay certain calls.)