Pregunta

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::threads 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.)

¿Fue útil?

Solución

  1. No, the standard doesn't guarantee that the first thread gets the lock first. Basically, if you need to impose and ordering between threads, you'll need to synchronize between these threads. Even if the first thread gets to call the mutex lock function first, the second thread may acquire the lock first.
  2. Absolutely. For example, there may be just one core available to your application at the time the threads are spawned and if the spawning thread decides after the second thread is spawned to wait on something, the schedule may decide to process the latest thread seen which is the second thread. Even if there are many cores available there are plenty of reasons the second thread is faster.
  3. No, why would it! The first step is to spawn a thread and carry on. By the time the first function object is called the second thread can be running and call its function object.
  4. No. There are not ordering guarantees between threads unless you explicitly impose them yourself as they would defeat the purpose of concurrency.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top