C ++ 11多线程锁和原子基元

时间:2013-09-15 22:45:49

标签: c++ multithreading c++11 concurrency atomic

(导致两个问题的问题)

在C ++ 11中是锁原子,在那个可以肯定保证两个线程不能同时获取锁?

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

最后,为什么我们需要std :: lock()才能使用原子原语(显然我们知道原子)并将其设置为1或0,具体取决于是否是否获得了锁定?

3 个答案:

答案 0 :(得分:4)

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

除此之外:互斥锁的重点是最多一个线程可以获取互斥锁,即保证没有两个线程可以获得相同的锁。关于使用原子变量来指示类似于锁的东西,您需要知道锁定/解锁互斥锁会增加额外的同步,超出通过更改原子值所做的事情:仅知道只有一个线程是不够的修改共享状态,还需要发出对系统进行的更改的信号。此外,正常锁定可能会在无法获取锁定时挂起线程执行(虽然我认为不需要执行实现,即我认为它可以使用自旋锁执行繁忙等待。)

答案 1 :(得分:2)

锁定是100%原子的,除非你试图做一些聪明的东西,比如在有人获取它时摧毁它。

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

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

话虽如此,有一种锁定结构就是这种方式,称为自旋锁,如果你能指望它只锁定几个周期就会非常快。

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

答案 2 :(得分: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()通过等待线程可以获取这两个互斥锁来避免这种情况,而不会阻止其他线程同时锁定它们。