gpt4 book ai didi

c++ - 基本自旋锁互斥体实现顺序

转载 作者:行者123 更新时间:2023-12-02 03:04:05 29 4
gpt4 key购买 nike

有一种流行的自旋互斥锁版本,该版本在Internet上广泛传播,在Anthony Williams的书(《 C++ Concurrency in Action》)中可能会遇到。这里是:

class SpinLock
{
std::atomic_flag locked;
public:
SpinLock() :
locked{ATOMIC_FLAG_INIT}
{
}
void lock()
{
while(locked.test_and_set(std::memory_order_acquire));
}
void unlock()
{
locked.clear(std::memory_order_release);
}
};

我不明白的是,为什么每个人都将 std::memory_order_acquire用作RMW操作的 test_and_set。为什么不是 std::memory_acq_rel
假设我们有两个线程同时尝试获取锁:
T1: test_and_set -> ret false
T2: test_and_set -> ret false

这种情况应该是可能的,因为我们有2个 acquire操作,它们之间没有任何 sync with关系。是的,在解锁互斥锁之后,我们执行了 release操作,该操作将引导后续的 release sequence进入,生活变得丰富多彩,每个人都很高兴。但是,为什么在前往 release sequence之前是安全的呢?

由于很多人都确切地提到了该实现,因此我认为它应该可以正常工作。那我想念什么呢?

更新1:

我完全理解该操作是原子操作, lockunlock之间的操作不能超出关键部分。这不是问题。问题是我看不到上面的代码如何防止2个互斥锁同时进入关键部分。为了防止它发生,应该在2 lock之间的关系之前发生。有人可以使用C++标准概念向我展示代码是绝对安全的吗?

更新2:

好的,我相信我们已经接近正确答案。我在标准中找到以下内容:

[atomics.order]条款11

Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.



在这个重要的注释上,我可以很高兴地结束这个问题,但我仍然有我的疑问。那 in the modification order部分呢?
Standard非常清楚:

[intro.multithread]子句8

All modifications to a particular atomic object M occur in some particular total order, called the modification order of M . If A and B are modifications of an atomic object M and A happens before(as defined below) B, then A shall precede B in the modification order of M , which is defined below.



因此,根据具有最新写入值的RMW操作的该条款,最新写入操作应发生在读取部分或RMW操作之前。这不是问题所在。正确的?

更新3:

我越来越认为自旋锁的代码已损坏。这是我的理由。 C++指定3种类型的操作:
  • 获取,发布,获取-发布-这些是同步操作。
  • 放松-这些不是同步操作
  • RMW-这些是具有“特殊”特征的操作

  • 让我们从RMW开始,了解它们的特殊之处。首先,它们是形成 release sequence的宝贵 Assets ,其次,它们具有上面引用的特殊子句([atomics.order]子句11)。我没有发现其他特别之处。

    获取/发布是同步操作和 release sync with acquire,因此形成了 happens before关系。宽松的操作只是简单的原子,根本不参与修改顺序。

    我们的代码中有什么?我们有一个RMW操作,该操作使用获取内存语义,因此,每当达到第一个解锁(释放)时,它都会扮演2个角色:
  • 与上一个sync with
  • 形成 release关系
  • 它参与release sequence
    但这只有在第一个unlock完成后才是正确的。

  • 在此之前,如果我们有2个以上的线程同时运行我们的 lock代码,那么我们可以同时输入通过 lock的代码,因为2个 acquire操作没有任何形式的关系。它们像宽松的操作一样无序。由于它们是无序的,因此我们无法使用有关RMW操作的任何特殊子句,因为没有 happens before关系,因此也没有 locked标志的修改顺序。

    因此,要么我的逻辑有缺陷,要么代码被破坏了。拜托,谁知道真相,请对此发表评论。

    最佳答案

    Could someone show me, using the C++ standard notions, that the code is perfectly safe?



    我最初与您有同样的担忧。我认为关键是要了解 std::atomic_flag变量的操作对于所有处理器/内核都是原子的。不管指定的内存顺序如何,在单独的线程中执行两个原子性的“测试和设置”操作都不能同时成功,因为它们然后不能是原子性的。该操作必须应用于实际变量,而不是缓存的本地拷贝(我认为,这甚至不是C++中的概念)。

    完整的推理链:

    29.7 p5(谈论测试和设置操作):

    Effects: Atomically sets the value pointed to by object or by this to true. Memory is affected according to the value of order. These operations are atomic read-modify-write operations (1.10). Returns: Atomically, the value of the object immediately before the effects.



    1.10第6页:

    All modifications to a particular atomic object M occur in some particular total order, called the modification order of M ...



    因此,如果在这种情况下,两个线程试图同时锁定自旋锁,则其中一个必须是第一个,另一个必须是第二个。现在,我们只需要显示第二个线程将必然返回该标志已经设置,从而防止该线程进入关键部分。

    第6段继续说:

    ... If A and B are modifications of an atomic object M and A happens before (as defined below) B, then A shall precede B in the modification order of M , which is defined below. [ Note: This states that the modification orders must respect the “happens before” relationship. — end note ]



    在两个线程中发生的两个测试和设置操作之间没有“先发制人”的关系,因此我们无法确定修改顺序中哪一个先出现。但是,由于p6中的第一句话(表明存在总体顺序),所以肯定有一个必须先于另一个。现在,从29.3 p12开始:

    Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.



    这表明测试和设置有序的第二个必须看到测试和设置有序的第一个写入的值。任何获取/发布选择均不影响此。

    因此,如果“同时”执行两个测试设置操作,它们将被赋予任意顺序,第二个将看到由第一个设置的标志值。因此,为测试和设置操作指定的内存顺序约束无所谓;它们用于控制在获取自旋锁期间的写入其他变量的顺序。

    对问题“Update 2”的答复:

    So according to that clause for an RMW operation to have the latest written value, the latest write operation should happen before the reading part or RMW operation. Which is not the case in the question. Right?



    纠正不存在“之前发生”的关系,但纠正不正确,RMW操作需要这样的关系才能保证最新的写入值。您作为“[atomics.order]子句11”列出的语句不需要“先发生”关系,只是原子标记的“修改顺序”中的一个操作在另一个操作之前。条款8规定将有这样的命令,并且将是总命令:

    All modifications to a particular atomic object M occur in some particular total order, called the modification order of M ...



    ...然后继续说,总排序必须与任何“先发生”关系保持一致:

    ... If A and B are modifications of an atomic object M and A happens before (as defined below) B, then A shall precede B in the modification order of M, which is defined below.



    但是,在没有“先发生”关系的情况下,仍然存在总排序-只是该排序具有一定程度的任意性。也就是说,如果A和B之间没有“先于”关系,则不指定A是在B之前还是之后排序。但是它必须一个或另一个,因为存在 特定总订单

    那么,为什么需要memory_order_acquire?

    诸如自旋锁之类的互斥锁通常用于保护其他非原子变量和数据结构。锁定自旋锁时使用 memory_order_acquire可确保从此类变量中读取内容将看到正确的值(即,以前保存自旋锁的任何其他线程写入的值)。对于解锁,还需要 memory_order_release,以便允许其他线程查看写入的值。

    获取/释放既可以防止编译器对锁的获取/释放之后的读/写进行重新排序,也可以确保生成任何必要的指令来确保适当级别的缓存一致性。

    进一步的证据:

    首先,来自29.3的注释:

    Note: Atomic operations specifying memory_order_relaxed are relaxed with respect to memory ordering. Implementations must still guarantee that any given atomic access to a particular atomic object be indivisible with respect to all other atomic accesses to that object. — end note



    这实际上是在说,指定的内存顺序不会影响原子操作本身。该访问必须“相对于所有其他原子访问是不可分割的”,包括来自其他线程的访问。要允许两个测试设置操作读取相同的值,将有效地划分至少其中之一,以使其不再是原子的。

    另外,从1.10第5段开始:

    In addition, there are relaxed atomic operations, which are not synchronization operations, and atomic read-modify-write operations, which have special characteristics.



    (测试和设置属于后一类),尤其是:

    “Relaxed” atomic operations are not synchronization operations even though, like synchronization operations, they cannot contribute to data races.



    (强调我的)。如果两个线程同时执行原子测试并设置(并且都执行“设置”部分)的情况将是这种数据争用,因此该文本再次表明这不可能发生。

    1.10第8页:

    Note: The specifications of the synchronization operations define when one reads the value written by another. For atomic objects, the definition is clear.



    这意味着一个线程读取另一线程写入的值。它说,对于原子对象,定义是明确的,这意味着不需要其他同步-对原子对象执行操作就足够了;其他线程将立即看到效果。

    特别是1.10 p19:

    [ Note: The four preceding coherence requirements effectively disallow compiler reordering of atomic operations to a single object, even if both operations are relaxed loads. This effectively makes the cache coherence guarantee provided by most hardware available to C ++ atomic operations. — end note ]



    请注意,即使存在宽松的负载,也要提到缓存一致性。这清楚地表明测试集一次只能在一个线程中成功,因为要使一个失败,要么缓存一致性被破坏,要么操作不是原子的。

    关于c++ - 基本自旋锁互斥体实现顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30691135/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com