Search code examples
c++concurrencymutexboost-thread

Acquire lock as soon as it's available


I have two threads trying to lock the same boost::mutex. One of those threads is continuously processing some data, and the other is periodically displaying the current state. The processing thread, according to my intention, releases the lock very frequently and reacquires it, so that the display thread can tap in and acquire it whenever it needs it. So, obviously, I would like the display thread to acquire the lock next time it's released by the process thread. However, it doesn't do that, instead, it waits for the lock and only acquires it after many lock-release cycles from the process thread.

Please inspect the minimal example illustrating my problem:

#include <boost/thread.hpp>
#include <iostream>

using namespace std;
using namespace boost;

mutex mut;

void process() {
        double start = time(0);
        while(1) {
                unique_lock<mutex> lock(mut);
                this_thread::sleep(posix_time::milliseconds(10));
                std::cout<<".";
                if(time(0)>start+10) break;
        }
}

int main() {

        thread t(process);

        while(!t.timed_join(posix_time::seconds(1))) {
                posix_time::ptime mst1 = posix_time::microsec_clock::local_time();
                cout<<endl<<"attempting to lock"<<endl;
                cout.flush();

                unique_lock<mutex> lock(mut);

                posix_time::ptime mst2 = posix_time::microsec_clock::local_time();
                posix_time::time_duration msdiff = mst2 - mst1;
                cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl;
                cout.flush();
        }

}

Compiled with: g++ mutextest.cpp -lboost_thread -pthread

When I run the executable, a sample output is like this:

...................................................................................................
attempting to lock
....................................................................................................................................................................................................................................................................................................................................................................................................................................
acquired lock in: 4243
...................................................................................................
attempting to lock
........................................................................................................
acquired lock in: 1049
...................................................................................................
attempting to lock
........................................................................................................................
acquired lock in: 1211
....................................

As you can see, in the worst case, the display thread waits for 424 lock-release cycles before it comes round to catching the lock.

I'm obviously using the mutex in a wrong way, but what is the usual way to solve this?


Solution

  • I've solved the problem using conditions, as suggested by Dale Wilson an FKaria, but I've used it in the opposite direction. So, the process thread checks for a pause flag, and when it's set, it waits for a condition, therefore releasing the lock. The display thread controls the pause flag, and it also resumes the process thread by notifying it through the condition. Code: (The code is mostly the same, I've marked the new lines with //New)

    #include <boost/thread.hpp>
    #include <boost/thread/condition.hpp>
    #include <iostream>
    
    using namespace std;
    using namespace boost;
    
    mutex mut;
    condition cond;
    
    volatile bool shouldPause = false; //New
    
    void process() {
            double start = time(0);
            while(1) {
                    unique_lock<mutex> lock(mut);
    
                    if(shouldPause) cond.wait(mut); //New
    
                    this_thread::sleep(posix_time::milliseconds(10));
                    std::cout<<".";
                    if(time(0)>start+10) break;
            }
    }
    
    int main() {
    
            thread t(process);
    
            while(!t.timed_join(posix_time::seconds(1))) {
                    posix_time::ptime mst1 = posix_time::microsec_clock::local_time();
                    cout<<endl<<"attempting to lock"<<endl;
                    cout.flush();
                    shouldPause = true; // New
                    unique_lock<mutex> lock(mut);
    
                    posix_time::ptime mst2 = posix_time::microsec_clock::local_time();
                    posix_time::time_duration msdiff = mst2 - mst1;
                    cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl;
                    cout.flush();
    
                    shouldPause = false; // New
                    cond.notify_all(); // New
            }
    
    }
    

    Now the output is precisely as I would like it to be:

    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 8
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 8
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 8
    ...................................................................................................
    attempting to lock
    .
    acquired lock in: 9
    ..........................