Why use defer lock with unique lock

156 Views Asked by At

Why use defer lock? What is with taking ownership first then locking? Isn't taking ownership and locking kinda same. What if we took ownership using unique lock of a mutex and not locking, could it still be prone to race conditions?

    void fun1(std::mutex &m){
        std::lock_guard<std::mutex> mlock(m);
        //multiple lines
    }

    void fun2(std::mutex &m){
        std::unique_lock<std::mutex,std::defer_lock> mlock(m);
        //multiple lines
        mlock.lock();
        //multiple lines
    }
1

There are 1 best solutions below

1
Solomon Slow On

What if we took ownership...?

This does not "take ownership" of the mutex m:

std::unique_lock<std::mutex,std::defer_lock> mlock(m);

In effect, it says, I want to construct this RAII object, mlock now, but I am not going to lock m (i.e., "take ownership of m") until later.

Could it still be prone to race conditions?

Absolutely. Yes.

{
    std::unique_lock<std::mutex,std::defer_lock> mlock(m);
    // Here, we still are prone to race conditions because m is not locked.
    ...
    std::lock(m);
    // Now, it's safe to use whatever data m protects.
    ...
}
// And here, m is guaranteed to be unlocked again.
...

Response to comment, below

Any use cases for using defer lock?

Only use case I can think of* is similar to what's shown on the cppreference web page to which @Mat referred you.

  1. You want to lock two or more mutexes, and
  2. You want to control the order in which the mutexes are locked so that you can avoid deadlock, and
  3. You want to use RAII to ensure that there's no way for a thread to leave the block without unlocking all of the mutexes.

So, you write something like this:

{
    // Create RAII objects. `defer_lock` means, "Just create the objects,
    //  but don't actually lock the mutexes. I solemnly swear that
    //  that _I_ will lock them before this story ends."
    std::unique_lock<std::mutex, std::defer_lock> ulk_A(mutex_A);
    std::unique_lock<std::mutex, std::defer_lock> ulk_B(mutex_B);

    // lock both mutexes
    if (...Deadlock avoidance requires A to be locked first...) {
        std::lock(mutex_A);
        std::lock(mutex_B);
    }
    else {
        std::lock(mutex_B);
        std::lock(mutex_A);
    }
    // _Now_ it's safe to access any shared data that are protected by
    // mutex_A or mutex_B.
    ...
}
// Can't leave the block without both mutexes being unlocked again.

* Maybe there's other uses. Just because *I* can't imagine something doesn't mean it's not out there waiting for somebody else to imagine it.