C++ 11:原子变量:lock_free属性:这是什么意思?

tsh*_*h06 2 c++ atomic c++11

我想了解c ++ 11中原子变量的lock_free属性是什么意思.我确实在这个论坛上搜索了其他相关问题但仍然有部分理解.感谢是否有人能够以简单的方式端到端地解释它.

Jer*_*fin 7

最简单的方法可以从谈论如果没有锁定会发生什么事情开始.

处理大多数原子任务最明显的方法是锁定.例如,要确保一次只有一个线程写入变量,可以使用互斥锁保护它.任何要写入变量的代码都需要在执行写操作之前获取互斥锁(并在之后释放它).只有一个线程可以一次拥有互斥锁,因此只要所有线程都遵循协议,任何给定时间都不能超过一个.

但是,如果你不小心,这可能会陷入僵局.例如,假设您需要将两个不同的变量(每个都由互斥锁保护)写为原子操作 - 即,您需要确保在写入一个变量时,您还要写入另一个变量.在这种情况下,如果你不小心,你可能会导致死锁.例如,让我们调用两个互斥锁A和B.线程1获取互斥锁A,然后尝试获取互斥锁B.同时,线程2获取互斥锁B,然后尝试获取互斥锁A.因为每个互斥锁持有一个互斥锁既不能同时获得两者,也不能朝着目标前进.

有各种策略可以避免它们(例如,所有线程总是试图以相同的顺序获取互斥锁,或者在合理的时间段内未能获得互斥锁,每个线程释放它所持有的互斥锁,等待一些随机数量的时间,然后再试一次).

但是,使用无锁编程,我们(显然足够)不使用锁.这意味着上面的死锁根本不会发生.如果操作正确,您可以保证所有线程不断向目标前进.流行的看法相反,它不是指的是代码必然的运行速度比使用锁以及编写的代码得更快.但是,它确实意味着消除了上述(以及一些其他类型的问题,如活锁和某些类型的竞争条件)等死锁.

现在,至于你是如何做到这一点的:答案简短而简单:它变化很大 - 很广泛.在很多情况下,您正在寻找特定的硬件支持,以原子方式执行某些特定操作.您的代码要么直接使用它们,要么扩展它们以提供更高级别的操作,这些操作仍然是原子的并且无锁.甚至可能(尽管很少实际)在没有硬件支持的情况下实现无锁原子操作(但鉴于其不切实际,我将继续尝试更详细地介绍它,至少目前如此).


Ker*_* SB 5

Jerry 已经提到了锁的常见正确性问题,即它们很难理解和正确编程。

锁的另一个危险是,您会失去执行时间的确定性:如果已获取锁的线程被延迟(例如,被操作系统取消调度,或“换出”),则整个程序可能会被延迟,因为它正在等待锁。相比之下,无锁算法总是能保证取得一些进展,即使任意数量的线程在其他地方被阻塞。

总的来说,无锁编程通常比使用非原子操作的锁定编程慢(有时明显慢),因为原子操作会对缓存和管道造成重大影响;然而,它提供了确定性和延迟上限(至少是进程的总体延迟;正如 @J99 观察到的,只要有足够多的其他线程正在取得进展,单个线程可能仍然处于饥饿状态)。你的程序可能会变慢很多,但它永远不会完全锁定,并且总是取得一些进展。

硬件架构的本质允许某些小操作本质上是原子的。事实上,这对于任何支持多任务和多线程的硬件来说都是非常必要的。在任何同步原语(例如互斥体)的核心,您需要某种原子指令来保证正确的锁定行为。

因此,考虑到这一点,我们现在知道某些类型(例如布尔值和机器大小的整数)可以原子地加载、存储和交换。因此,当我们将这样的类型包装到std::atomic模板中时,我们可以预期生成的数据类型确实会提供不使用锁的加载、存储和交换操作。相比之下,您的库实现始终允许将原子实现为由锁保护的Foo普通原子。Foo

要测试原子对象是否是无锁的,可以使用is_lock_free成员函数。此外,还有一些ATOMIC_*_LOCK_FREE宏可以告诉您原子基元类型是否可能具有无锁实例化。如果您正在编写想要无锁的并发算法,那么您应该包含一个断言,表明您的原子对象确实是无锁的,或者对宏进行静态断言以使其具有值2(这意味着相应类型的每个对象始终是无锁的)。