C ++ 11多线程锁和原子基元
(一个问题导致两个问题)
在C ++ 11中锁是原子的,因为可以肯定的是两个线程不能同时获得锁?
如果上述答案为“是”,那么双重锁定的目的是什么?
最后,为什么我们只需要使用原子基元(显然,我们知道它是原子的 )就需要std :: lock(),并根据是否获取锁定将其设置为1或0?
双重检查锁定背后的混淆理念是,在检查条件时不获取锁定,只能在不可能的情况下获取锁定。 然而,要实现这一点,仍然需要相当多的同步,这很容易出错。 由于双重检查锁定的主要用途是初始化singleton(它们本身就是个坏主意)并且在线程间共享常量对象,所以C ++ 11实际上实现了函数本地static
对象的线程安全初始化。 这应该消除人们试图获得双重检查锁定权的大多数情况。
除此之外:互斥锁的意义在于,至多有一个线程可以获得互斥锁,即保证没有两个线程可以获得相同的锁。 关于使用一个原子变量来表示与锁相似的东西,您需要知道,锁定/解锁一个互斥锁会增加额外的同步,超出了通过更改原子值所做的操作:仅仅知道一个线程是不够的修改共享状态,还需要发信号通知系统正在进行的更改。 另外,当无法获取锁时,正常的锁可能会暂停执行线程(尽管我不认为需要这样做,也就是说,我认为它可以使用自旋锁进行繁忙的等待)。
锁定是100%原子的,除非你试图做一些聪明的事情,比如在某人拿到它的时候摧毁它。
锁定花费时间。 如果您只需要一小部分时间检查锁定,双重锁定可以让您避免锁定成本,并且只有当您无法证明可以安全地跳过锁定时才需要锁定。
你不能简单地用原子基元替换一个锁,因为你可以等待一个锁。 如果您等待锁定,则操作系统将停止运行该线程,并在其他地方使用其CPU功率。 如果你坐在一个原子基元上循环,你会让CPU忙碌而不做有用的事情。
这就是说,有一种以这种方式构建的锁结构,称为自旋锁,如果您只能锁定几个周期,则锁结构非常快。
此外,你不能使用带有原子变量的condition_variables,你需要一个真正的锁
是的,C ++ 11锁是原子的:一次只有一个线程可以拥有一个std::mutex
。 这是一个互斥的完整点:提供互斥。
双重检查锁定的目的是为了避免在不需要时获取锁的开销:特别是为了避免当多个线程同时运行相同的代码并且不再需要相互排除时避免相互排斥和随后的序列化。
这通常用于某种形式的一次性初始化,并且仍然需要同步。 这种同步可以通过原子来完成,但很难做到。 我提到了如何在我写的博客文章(Lazy Initialization和Double Checked Locking with Atomics)中做到这一点,但通常只需使用本地static
对象或std::call_once
。
在某些情况下,互斥体可以用一个原子标志替换,但是正确地获得同步是很困难的:通常这个标志只是表明其他数据可以被访问,并且你需要确保这个数据被正确地同步。 除非分析表明互斥体是一个瓶颈,否则您最好坚持一个互斥体,而不是尝试使用原子技术来实现自己的同步。
最后, std::lock()
是在没有死锁的情况下锁定多个互斥体。 通常你必须一次锁定一个互斥锁。 但是,如果线程1锁定了互斥量A然后互斥量B,并且线程2锁定了互斥量B然后互斥锁A,您可能会死锁。 std::lock()
通过等待直到线程可以获取两个互斥体,而不会阻止其他线程同时锁定它们来避免此问题。