c++11 timed_mutex behaves different from mutex when calling "try_lock_for"

I was expecting that if timed_mutex.try_lock_for could success for all calling threads, like this:

timed_mutex tm;
atomic<int> atTm(0);
void tTMutex(int i) {
    this_thread::sleep_for(chrono::microseconds(200 * i));
    if (tm.try_lock_for(chrono::milliseconds(200 * i))) {
        atTm.fetch_add(1);
    }
}

int main() {
    thread t[3];
    for (size_t i = 0; i < size(t); ++i) {
        t[i] = thread(tTMutex, i);
    }
    ready = true;
    for (size_t i = 0; i < size(t); ++i) {
        t[i].join(); // seems only 1 thread increased atTm
    }
    cout << atTm;
    return 0;
}

It prints "1", while I expected that it could print "3". As long as 3 threads call tm.try_lock_for in different time, I think they should all successfully return, but in fact not: what’s the reason for this?

I changed my program slightly, using ordinary mutex instead of timed_mutex, as below:

mutex mAllWait; // change 1
atomic<int> atAll(0);
void tMutex() {
    unique_lock<mutex> lk(mAllWait); // change 2
    this_thread::sleep_for(chrono::milliseconds(200));
    atAll.fetch_add(1);
}

int main() {
    thread t[3];
    for (size_t i = 0; i < size(t); ++i) {
        t[i] = thread(tMutex);
    }
    ready = true;
    for (size_t i = 0; i < size(t); ++i) {
        t[i].join(); // all threads can increase atAll
    }
    cout << atAll;
    return 0;
}

It prints 3, as I expected. So what’s the core difference between the 2 programs?

>Solution :

You are not unlocking the mutex after acquiring the lock in the first version:

if (tm.try_lock_for(chrono::milliseconds(200 * i))) {
    atTm.fetch_add(1);
    tm.unlock(); // missing
}

so only the first thread will be able to ever acquire it. That’s why std::unique_lock should be preferred. It unlocks in its destructor. It also accepts std::timed_mutex as template argument and has the try_lock_for member function has well.


That aside:

There is no guarantee that try_lock_for will wait as long as you specified. It may wake up spuriously before the time limit is reached and return false.

That could happen immediately when you start waiting or at any other time before the mutex can be acquired or even if the mutex is currently not acquired by any other thread. Then you don’t increment atTm in that thread.

Of course there is also the possibility that the try_lock_for calls actually do time out and you wont increment for that reason, but I assume as long as you don’t run this on a heavy overloaded system that shouldn’t happen.

Run the try_lock_for call in a loop and check whether the time has actually exceeded if you care about that. This is probably simpler to do with try_lock_until.

Leave a Reply