1

(一个问题引出两个问题)

在 C++11 中,锁是原子的,因此可以确定两个线程不能同时获取锁?

如果上面的答案是“是”,那么双重检查锁定的目的是什么?

最后,为什么我们需要 std::lock() 当我们可以只使用一个原子原语(显然 - 我们知道它是atomic)并根据是否已获得锁将其设置为 1 或 0 ?

4

3 回答 3

4

双重检查锁定背后令人困惑的想法是在检查条件时获取锁,而仅在不太可能的情况下获取锁。然而,为了让它工作,仍然需要相当多的同步,这很容易出错。由于双重检查锁定的主要用途是初始化单例(这本身就是个坏主意)和跨线程共享的常量对象,因此 C++11 实际上实现了函数本地static对象的线程安全初始化。这应该消除人们试图获得双重检查锁定权的大多数情况。

除此之外:互斥锁的要点是最多一个线程可以获取一个互斥锁,即保证没有两个线程可以获取同一个锁。关于使用原子变量来表示类似于锁的东西,您需要注意锁定/解锁互斥锁会增加额外的同步,而不是通过更改原子值来完成:仅知道只有一个线程是不够的修改共享状态时,还需要发出对系统所做更改的信号。此外,当无法获取锁时,普通锁可能会暂停线程执行(尽管我认为不需要执行,即我认为它可以使用自旋锁进行忙碌等待)。

于 2013-09-15T22:56:16.433 回答
2

锁是 100% 原子的,除非你尝试做一些聪明的事情,比如在有人获取它时销毁它。

锁定需要时间。在您只需要在一小部分时间内检查锁的情况下,双重检查锁可以让您避免锁成本,并且只有在您无法证明跳过锁是安全的时候才需要锁。

您不能简单地用原子原语替换锁,因为您可以等待锁。如果您等待锁定,操作系统将停止运行该线程,并将其 CPU 功率消耗在其他地方。如果你坐在一个原子原语上循环,你就会让 CPU 忙碌而不做有用的事情。

话虽如此,有一种以这种方式构建的锁结构,称为自旋锁,如果您可以期望它只锁定几个周期,那么它的速度非常快。

此外,您不能将 condition_variables 与原子变量一起使用,您需要一个真正的锁

于 2013-09-15T22:55:54.027 回答
2

是的,C++11 锁是原子的:一次只有一个线程可以拥有一个锁std::mutex。这就是互斥锁的全部意义:提供互斥。

双重检查锁的目的是避免在不需要时获取锁的开销:特别是当多个线程同时运行同一位代码并且不再需要互斥时避免互斥和随之而来的序列化.

这通常用于某种形式的一次性初始化,并且仍然需要同步。这种同步可以用原子来完成,但很难做到。我确实在我写的一篇博文(Lazy Initialization and Double Checked Locking with Atomics)中提到了如何做到这一点,但你通常最好只使用本地static对象,或者std::call_once.

在某些情况下,互斥体可以用原子标志替换,但要正确同步很难:通常该标志只是表明可以访问其他数据的一些指示,您需要确保这些数据正确同步。除非分析表明互斥锁是一个瓶颈,否则最好坚持使用互斥锁而不是尝试使用原子来滚动自己的同步。

最后,要点std::lock()是一次锁定多个互斥体而不会出现死锁。通常,您必须一次锁定一个互斥锁。但是,如果线程 1 锁定了互斥量 A,然后锁定了互斥量 B,线程 2 锁定了互斥量 B,然后又锁定了互斥量 A,则可能会出现死锁。std::lock()通过等待线程可以同时获取两个互斥体来避免这种情况,同时又不阻止其他线程锁定它们。

于 2013-09-16T07:43:42.093 回答