Process while waiting for mutex lock

1.5k Views Asked by At

Is there some way to execute a code section while waiting for a mutex to lock?

The only true internal hit to my application's performance is database interaction, and there are times where I need strict synchronization which because of the database interaction may cause contention, so I'd like to use the time waiting for a lock to get data from the database.

For example, I'd like the code to look something like, in pseudo:

boost::unique_lock<boost::mutex> process_lock(process_mutex, work_while_contending);
//code to execute while lock contending
process_lock.proceed_after_lock();

I've examined boost's synchronization section, and while futures and recursive sound like my intent could be achieved, but I can't figure out how to implement my intent.

How can my intent be implemented?

2

There are 2 best solutions below

2
On BEST ANSWER

You don't really want to do this in most cases. If you do want to do it, you probably want to use futures, like this:

auto f = std::async(std::launch::async, [&]() {
   // do work in another thread here.
});

boost::unique_lock<boost::mutex> process_lock(process_mutex);
auto result = f.get();

// proceed here the work is done and you have the lock

... This lets you do work in another thread while the original thread is waiting for the mutex. Once both the work is complete and the mutex is acquired the thread continues. If the work finishes before the mutex is acquired then the work thread will go away and the original thread will just be idle waiting for the mutex.

Here's why you don't want to do this: If you acquire the mutex before the work completes then no other thread will be able to acquire the mutex, the program will stall while it waits for the "background" work to complete.

A possible solution is to use a busy wait loop with a non-blocking try_lock. The potential problem here is that the thread is not waiting for the mutex, so there is no guarantee that the thread will ever get ahold of it. If there is a lot of contention for it, and others are actively waiting, then you'll never get it. If you wait for it, then typically the OS will guarantee some sort of order such that you will eventually get it. If you're not waiting, that guarantee cannot be made.

0
On

This is actually very easy. There's a different constructor you can call for the lock that requests the actual lock on the mutex to be deferred. You can then call the try_lock() method to attempt to obtain the lock, which is a non-blocking call that returns a boolean indicating whether the lock was successfully taken or not. In your case, you could use this method like so:

boost::unique_lock<boost::mutex> process_lock(process_mutex, boost::defer_lock_t);
while(!process_lock.try_lock())
{
    ...do some work
}

Once the mutex is successfully obtained by the lock, it will take responsibility for unlocking it again like normal, so you haven't introduced any problems with exception handling.

EDIT: Just had another thought about this question. The answer I gave is great if there's a task you want to run each time a lock attempt fails, but if the work you wanted to do only needed to be done a single time if the lock could not be obtained on the first try, the following approach would be more suitable:

boost::unique_lock<boost::mutex> process_lock(process_mutex, boost::try_to_lock_t);
if(!process_lock.owns_lock())
{
    ...do some work
    process_lock.lock();
}