简体   繁体   中英

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?

It's not that you're using the mutex wrong, just that threading doesn't do what you expect here. The OS decides which thread runs when (this is known as "scheduling"), and there's nothing in the code that forces a thread switch at the end of the loop; the thread keeps going, and reacquires the lock. One thing to try is to add a call to this_thread::yield() after releasing the lock (in this case, at the top of the loop, before relocking); that will suggest to the scheduler that it's appropriate for another thread to run. If you really want tightly synchronized interleaving, threads won't do it; instead, write a higher-level function that calls your two functions one after the other.

If the update thread has nothing else to do it can wait for the mutex to become available.

Look at boost::condition_variable. You can read about it here http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html and here Using boost condition variables

If having the update thread go to sleep would be a problem -- it is on many GUI systems and you don't specify which one you are using -- consider posting a message from the processing thread to to updating thread. (again the details depend on your platform.) The message may either contain the information necessary to update the display, or be a notification that "now would be a good time to look."

If you do use a condition code, the processing thread should probably yield after signalling the condition and before reacquiring the lock to open up a large window for the updating thread.

You may want to have a look at boost::condition_variable , specially at the methods wait() and notify_one() or notify_all() . The method wait() will block the current thread until a the lock is released. The methods notify_one() and notify_all() notify one or all threads that are waiting for the lock to continue execution.

As I see it, the principle of mutexes is not fairness, but correctness. Mutexes themselves cannot control the scheduler. One thing that bothers me is the reason why you have chosen to create 2 threads, which use such coarse-grained locking. In theory you are running two things in parallel, but in fact you are making them interdependent/serial.

Pete's idea seem much nicer (one function running these draw & update) and you still can use threads inside each of inner functions, not having to worry about contention and fairness so much.

If you really wish to have two threads running in parallel, then I can give you just a few tips:

  • spinlock using atomics and forget about mutex,
  • go into implementation-defined land and set the thread priorities, or
  • make the locking much more fine-grained.

Neither of these is problem-proof, unfortunately.

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
..........................

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM