What's the best way to lock multiple std::mutex'es?
Asked Answered
N

2

62

Note: This question concerns C++11. The answer to the same question in C++17 (or later revisions) may have changed. For details:


When we want to lock multiple std::mutex'es, we use std::lock(). But std::lock() does not provide RAII feature.

When we want to lock a std::mutex in RAII way, we use std::lock_guard. But std::lock_guard cannot lock multiple std::mutex'es safely.

Is there any way to take the advantages of both methods, to lock multiple std::mutex'es in RAII way?

Nailhead answered 14/6, 2013 at 16:56 Comment(0)
P
103

Yes, you can use a std::unique_lock with std::defer_lock. It tells the unique_lock to not lock the mutex immediately, but to build the RAII wrapper.

std::unique_lock<std::mutex> lk1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lk2(mutex2, std::defer_lock);
std::lock(lk1, lk2);

Due to its variadic nature std::lock is not bound to only two arguments but can be used with as many arguments as your compiler has support for.

Howard Hinnant also pointed out an interesting fact about performance, you can check this link if you are interested. He addresses performance concerns and shows that std::lock can be implemented efficiently, I can also recommend to read all the comments in that post.

Priester answered 14/6, 2013 at 17:0 Comment(0)
S
2

As you've pointed out, std::lock_guard in itself doesn't provide a deadlock-free way of locking multiple mutexs. Without a safe method, you run into the Dining Philosophers Problem.

std::lock implements a deadlock-free algorithm which locks multiple Lockable objects. It can be used with

std::mutex m1, m2;

{   // Option A - lock mutexes first, adopt later
    std::lock(m1, m2);
    std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);
    // critical section ...
}

{   // Option B - defer first, lock locks later
    std::unique_lock<std::mutex> lock1(m1, std::defer_lock);
    std::unique_lock<std::mutex> lock2(m2, std::defer_lock);
    std::lock(lock1, lock2);
    // critical section ...
}

{    // Option C - std::scoped_lock (C++17, but provided here for completeness)
    std::scoped_lock lock(m1, m2);
}

If you don't need the extra features that std::unique_lock provides (e.g. transferring ownership of the lock elsewhere), then std::lock_guard should be preferred.


Note: the examples show only two locks, but all methods work with arbitrarily many locks.

Sienese answered 10/9, 2023 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.