如何最好地锁定多个std::mutex?

61
注意:这个问题涉及到C++11。在C++17(或更高版本)中,对于同样的问题的答案可能已经改变。有关详细信息,请参考:
当我们想要锁定多个std::mutex时,我们使用std::lock()。但是std::lock()不提供RAII特性。
当我们想要以RAII方式锁定一个std::mutex时,我们使用std::lock_guard。但是std::lock_guard不能安全地锁定多个std::mutex。
有没有办法兼顾两种方法的优势,以RAII方式锁定多个std::mutex?
2个回答

102

是的,您可以使用带有std::defer_lockstd::unique_lock。这会告诉unique_lock不要立即锁定互斥锁,而是构建RAII包装器。

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

由于其可变参数的特性,std::lock不仅局限于两个参数,而是可以使用所有编译器支持的参数。

Howard Hinnant还指出了有关性能的有趣事实,如果您感兴趣,可以查看此链接。他解决了性能问题,并展示了std::lock的高效实现,我也建议阅读该帖子中的所有评论。


17
+1 而且 std::lock 不仅限于两个锁,它可以处理任意数量的锁(在变参模板上受编译器限制)。 - Howard Hinnant
2
有人对 std::lock 的效率提出了疑虑。请参考此答案:https://dev59.com/rGYr5IYBdhLWcg3wcJ10#14525010 来解决这些问题。 - Howard Hinnant

0
正如你所指出的,std::lock_guard本身并不能提供一种无死锁的方式来锁定多个互斥锁。没有安全的方法,你就会遇到哲学家就餐问题std::lock实现了一种无死锁算法,可以锁定多个可锁定对象。它可以与其他对象一起使用。
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);
}

如果您不需要std::unique_lock提供的额外功能(例如将锁的所有权转移给其他地方),那么应该优先选择std::lock_guard
注意:示例中只显示了两个锁,但所有方法都适用于任意数量的锁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接