标签: stdatomic

std::mutex 和 std::atomic 的链接问题

我有这个:

单例.h

#ifndef SINGLETON_H
#define SINGLETON_H

#include <atomic>
#include <mutex>

class Singleton
{
public:
    static std::atomic<Singleton*> Singleton::m_instance;
    static std::mutex Singleton::m_mutex;
    static Singleton* getInstance();

    Singleton();
    ~Singleton();
};

#endif
Run Code Online (Sandbox Code Playgroud)

单例.cpp

#include "Singleton.h"

Singleton::Singleton()
{
}

Singleton* Singleton::getInstance() 
{
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (tmp == nullptr) 
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) 
        {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

Singleton::~Singleton() {}
Run Code Online (Sandbox Code Playgroud)

主程序

#include "Singleton.h"
#include <iostream>
int main()
{
    Singleton* singleton …
Run Code Online (Sandbox Code Playgroud)

c++ multithreading c++11 stdatomic visual-studio-2013

5
推荐指数
1
解决办法
4226
查看次数

在 C++ 中,获取/释放原子访问和与栅栏结合的宽松访问之间有什么有效的区别吗?

具体来说,以下之间是否有任何有效的区别:

i = a.load(memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)

或者

a.store(5, memory_order_release);
Run Code Online (Sandbox Code Playgroud)

atomic_thread_fence(memory_order_acquire);
i = a.load(memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)

或者

a.store(5, memory_order_relaxed);
atomic_thread_fence(memory_order_release);
Run Code Online (Sandbox Code Playgroud)

分别?

非宽松原子访问是否提供信号栅栏和线程栅栏?

c++ multithreading memory-model memory-barriers stdatomic

5
推荐指数
1
解决办法
784
查看次数

从硬件角度看内存排序

我想在阅读了网上的一些材料后,我在某种程度上理解了内存排序保证的各个方面。然而,仅从软件和理论的角度来看规则似乎有点神奇。这里解释了为什么两个处理器似乎会重新排序的一个例子,它对我实际可视化该过程有很大帮​​助。所以我的理解是,预取器可以为一个处理器提前加载读取,而不会为另一个处理器加载读取,然后对于外部观察者来说,第一个处理器看起来比第二个处理器更早读取(并且现在可能有可能)在没有同步的情况下具有陈旧的价值),因此会看到指令重新排序。

之后,我实际上从 CPU 的角度寻找更多关于如何产生这种效果的解释。例如,考虑acquire-release栅栏。通常引用的一个经典示例如下:

thread-0: x.store(true,std::memory_order_release);
thread-1: y.store(true,std::memory_order_release);

thread-2:
while(!x.load(std::memory_order_acquire));
if(y.load(std::memory_order_acquire)) ++z;

thread-3:
while(!y.load(std::memory_order_acquire));
if(x.load(std::memory_order_acquire)) ++z;
Run Code Online (Sandbox Code Playgroud)

由于顺序一致性中不存在全序,因此线程 2 可以看到线程 0 首先执行其操作,然后是线程 1,而线程 3 可以看到线程 1 首先执行其操作,然后是线程 0。这z==0可能是一个可能的结果。

如果有一个解释(比如使用四个 cpu,每个 cpu 运行上面的一个线程)以及硬件中会发生什么让我们看到这种重新排序,那将非常有帮助。它不一定是非常复杂的现实世界详细案例(如果这是理解它的唯一方法,也可以是这样)。只是像上面链接的答案所做的那样的近似值,加上有关缓存(或任何参与因素)的内容,我想它应该为我(可能还有许多其他人?)做这件事。

另一种是:

thread-0:
x.store(true,std::memory_order_relaxed);
y.store(true,std::memory_order_release);

thread-1:
while(!y.load(std::memory_order_acquire)); // <------ (1)
if(x.load(std::memory_order_relaxed)) ++z;
Run Code Online (Sandbox Code Playgroud)

再次遵循规则,我可以理解这永远不会得到z==0(假设所有初始值都是 0)以及为什么更改(1)relaxed可能会得到我们z==0。但它再次显得有点神奇,直到我能想到它是如何在物理上发生的。

因此,任何使用足够数量的处理器及其缓存等进行解释的帮助(或指针)都将是巨大的。

c++ multithreading atomic memory-barriers stdatomic

5
推荐指数
0
解决办法
160
查看次数

我可以将 C11 `_Atomic` 关键字应用于枚举类型吗?

如果我有一个类型

enum foo {
    FOO,
    BAR,
    BAZ,
};
Run Code Online (Sandbox Code Playgroud)

然后我可以声明该类型的原子版本吗

_Atomic(enum foo);
Run Code Online (Sandbox Code Playgroud)

或者我必须使用egatomic_int并投射结果atomic_load()

以下程序编译时不会出现警告:

    #include <stdatomic.h>
    #include <stdio.h>

    enum foo {FOO, BAR, BAZ};

    int main(void) {
        _Atomic(enum foo) foo_a;
        atomic_store(&foo_a, BAR);
        enum foo val = atomic_load(&foo_a);
        printf("%u\n", val);
        return 0;
    }
Run Code Online (Sandbox Code Playgroud)

但也是如此:

    #include <stdatomic.h>
    #include <stdio.h>

    enum foo {FOO, BAR, BAZ};

    int main(void) {
        enum foo foo; // <---- non atomic
        atomic_store(&foo, BAR);
        enum foo val = atomic_load(&foo);
        printf("%u\n", val);
        return 0;
    }
Run Code Online (Sandbox Code Playgroud)

c enums c11 stdatomic

5
推荐指数
1
解决办法
1415
查看次数

MESI 协议和 std::atomic - 它是否确保所有写入对其他线程立即可见?

关于std::atomic,C++11 标准规定,存储到原子变量将在“合理的时间内”对该变量的加载变得可见。

\n\n

从 29.3p13 开始:

\n\n
\n

实现应该使原子存储在合理的时间内对原子加载可见。

\n
\n\n

然而,我很想知道在处理基于 MESI 缓存一致性协议(x86、x86-64、ARM 等)的特定 CPU 架构时实际会发生什么。

\n\n

如果我对 MESI 协议的理解是正确的,那么一个核心总是会立即读取先前写入/正在由另一个核心写入的值,可能是通过窥探它。(因为写入值意味着发出 RFO 请求,这反过来会使其他缓存行无效)

\n\n

这是否意味着当一个线程 A 将一个值存储到 an 中时std::atomic,另一个连续对该原子进行加载的线程 B 实际上总是会观察到 A 在 MESI 架构上写入的新值?(假设没有其他线程正在对该原子执行操作)

\n\n

\xe2\x80\x9csuccessively\xe2\x80\x9d 我的意思是在线程 A 发出原子存储之后。(修改顺序已更新)

\n

c++ cpu-architecture memory-model stdatomic mesi

5
推荐指数
1
解决办法
1011
查看次数

在具有获取一致性与宽松一致性的原子负载上旋转

考虑下面的代码:

// Class member initialization:
std::atomic<bool> ready_ = false;

...

// Core A:
while (!ready_.load(std::memory_order_acquire)) {
  // On x86, you would probably put a `pause` instruction here.
}
// Core A now accesses memory written by Core B.

...

// Core B:
// Core B writes memory.
ready_.store(true, std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)

假设Core A 和Core B 是两个不同的物理核心(即,它们不是共位于同一物理核心上的两个超线程)。上面的 Core A 代码的性能是否比下面的代码差或性能相同?请注意,核心 A 只是执行负载;这不是涉及写入的经典比较交换示例。我对几种架构的答案感兴趣。

// Core A:
while (!ready_.load(std::memory_order_relaxed)) {
  // On x86, you would probably put a `pause` instruction here.
}
std::atomic_thread_fence(std::memory_order_acquire);
// Core …
Run Code Online (Sandbox Code Playgroud)

c++ atomic stdatomic

5
推荐指数
1
解决办法
373
查看次数

为什么 [[carries_dependency]] 不是 C++ 中的默认值?

我知道这memory_order_consume已被弃用,但我试图理解原始设计中的逻辑以及如何工作[[carries_dependency]]以及kill_dependency应该如何工作。为此,我想要一个在 IBM PowerPC 或 DEC alpha 上中断的具体代码示例,甚至是一个假设的架构,该架构具有一个在 C++11 或 C++14 中完全实现消耗语义的假设编译器。

我能想到的最好的例子是这样的:

int v;
std::atomic<int*> ap;

void
thread_1()
{
  v = 1;
  ap.store(&v, std::memory_order_release);
}

int
f(int *p [[carries_dependency]])
{
  return v;
}

void
thread_2()
{
  int *p;
  while (!(p = ap.load(std::memory_order_consume)))
    ;
  int v2 = f(p);
  assert(*p == v2);
}
Run Code Online (Sandbox Code Playgroud)

我知道这段代码中的断言可能会失败。但是,如果您删除from ,断言是否应该失败?如果是这样,为什么会这样呢?毕竟,您请求了 a ,那么为什么您希望其他访问能够反映获取语义呢?如果删除不能使代码正确,那么有什么示例(或为所有变量设置默认值)会破坏正确的代码?[[carries_dependency]]fmemory_order_consumev[[carries_dependency]][[carries_dependency]][[carries_dependency]]

我唯一能想到的是,这也许与寄存器溢出有关?如果函数将寄存器溢出到堆栈上并稍后重新加载它,则可能会破坏依赖链。因此,[[carries_dependency]]在某些情况下可能会使事情变得高效(也就是说在调用此函数之前不需要在调用者中发出内存屏障),但也要求被调用者在任何寄存器溢出或调用另一个函数之前发出内存屏障,这在其他情况下可能效率较低案例?不过,我在这里抓住了救命稻草,所以仍然很想听听了解这些东西的人的意见......

c++ carries-dependency stdatomic relaxed-atomics

5
推荐指数
1
解决办法
207
查看次数

C11原子与序列点的关系

我基本上有以下代码片段:

size_t counter = atomic_fetch_sub_explicit(&atomicCounter, 1, memory_order_release);
if (counter - 1 == 0
    && atomic_load_explicit(&anotherAtomicCounter, 1, memory_order_relaxed) == 0 {
      //Some code
}
Run Code Online (Sandbox Code Playgroud)

为了正确性,重要的是 的原子加载anotherAtomicCounter发生在 的 fetch-and-sub (FAS) 之后atomicCounter。对于给定的内存顺序,通常无法保证这一点,并且加载可能会在 FAS 之前发生。但是,我想知道序列点如何影响这个特定的代码。标准中提到

如果评估 A 在评估 B 之前排序,则 A 评估将在 B 评估开始之前完成。

与规则 2 结合

在以下二元运算符的第一个(左)操作数评估之后和第二个(右)操作数评估之前有一个序列点:&&(逻辑与)、|| (逻辑或),和,(逗号)。

这意味着原子加载必须在比较之后发生,但只有在知道 FAS 的结果后才能完成比较。

我的问题是这些规则是否保证原子加载始终发生在 FAS 之后,即使使用更宽松的内存顺序也是如此?

提前致谢!

c atomic sequence-points language-lawyer stdatomic

5
推荐指数
1
解决办法
162
查看次数

C++ 原子和带有 RDMA 的内存顺序

当在现代无锁内存上使用单侧 RDMA 时,会出现这样的问题:如果数据对象跨越多个缓存行,远程读取器如何安全地查看其传入数据。

\n

在 Derecho 开源多播和复制日志库(位于https://GitHub.com/Derecho-Project上)中,我们有这种模式。写入器 W 被授予写入读取器 R 中的一系列内存的权限。内存已正确固定和映射。现在,假设写入涉及跨越许多缓存行的某种数据向量,这很常见。我们使用一个守卫:一个递增的计数器(也在 RDMA 可访问内存中,但在其他一些缓存行中)。R 旋转,当它看到变化时观察计数器\xe2\x80\xa6,这告诉 R \xe2\x80\x9c 你有一条新消息\xe2\x80\x9d,然后 R 读取向量中的数据。后来我们有第二种模式,R 对 W 说,\xe2\x80\x9c我已经处理完该消息,你可以发送另一条消息。\xe2\x80\x9d

\n

我的问题:对于现代内存模型,应该使用哪种 C++ 原子风格来写入向量的内存?这会被称为宽松一致性吗?我希望我的代码能够在 ARM 和 AMD 上运行,而不仅仅是具有强大 TSO 内存模型的英特尔。

\n

那么对于我的计数器,当 R 旋转监视计数器更新时,我希望如何声明计数器?是否需要将其声明为获取-释放原子?

\n

最后,在 R 观察到计数器已增加之后,就速度或正确性而言,将所有内容声明为宽松的,然后在此处使用内存顺序栅栏是否有任何优点?我的想法是,通过第二种方法,我在所有 RDMA 内存上使用最小一致性模型(并对所有此类内存使用相同的模型),而且我只需要在观察到计数器增加后调用成本更高的内存顺序栅栏。因此,在访问我的向量之前,它只发生一次,而每次我的轮询线程循环时,获取释放原子计数器都会触发内存防护机制。对我来说,这听起来非常昂贵。

\n

最后一个想法又引出了一个问题:我是否也必须将此内存声明为易失性,以便 C\xe2\x80\x94 编译器意识到数据可以在其脚下更改,或者编译器本身可以看到数据就足够了std::原子类型声明?在Intel上,对于全店订购,肯定需要TSO加上易失性。

\n

[编辑:新信息](我试图在这里吸引一些帮助!)

\n

一种选择似乎是将 RDMA 内存区域声明为 std::atomic<relaxed_consistency> 但每次我们的谓词评估线程重新测试防护时都使用锁(在 RDMA 内存中,将使用相同的宽松属性进行声明) )。我们将保留 C++ 易失性注释。

\n

原因是,使用具有获取-释放语义的锁,内存一致性硬件将被警告它需要隔离先前的更新。锁本身(互斥体)可以声明为谓词线程本地的,然后将存在于本地 DRAM 中,这是便宜的,并且由于这不是任何东西争用的锁,因此锁定它可能与 test_and_set 一样便宜,并且解锁只是写入 0。如果谓词为 true,我们的触发代码体将在访问锁之后运行(可能是在锁释放之后),因此我们建立所需的顺序以确保硬件将获取受保护的对象使用实际的内存读取。但是,通过谓词测试的每个周期(每次“旋转”),我们最终都会对每个谓词执行锁定获取/释放。所以这会导致一些速度减慢。

\n

选项二看似开销较小,也将 RDMA 区域声明为具有宽松一致性的 std::atomic,但省略了锁并像我们现在一样进行测试。然后,当谓词测试为真时,我们将使用语义执行显式内存栅栏(std::memory-order)。我们得到相同的屏障,但仅在谓词评估为 true 时才支付成本,因此开销更少。

\n

但现在我们遇到了一个不同类型的问题。Intel 有总存储顺序 TSO,并且由于任何线程都会执行一些先写后读操作,Intel …

c++ cpu-architecture memory-barriers rdma stdatomic

5
推荐指数
1
解决办法
360
查看次数

C++中变量的修改顺序是如何定义的?

我读过这个问答:与“(简单)发生在之前”相比,“强烈发生在之前”的意义是什么?

作者给出了一个有趣的评估的概述,该评估在 C++20 之前是不可能的,但显然从 C++20 开始是可能的:

.-- T3 y.store(3, seq_cst);                   --.                 (2)
|        |                                      | strongly
|        | sequenced before                     | happens
|        V                                      | before
|   T3 a = x.load(seq_cst); // a = 0    --.   <-'                 (3)
|                                         : coherence-
|                                         : ordered
|                                         : before
|   T1 x.store(1, seq_cst);             <-'   --. --.             (4)
|        |                                      |st |
|        | sequenced before                     |h  |
|        V                                      |b  |
| . T1 y.store(1, release);                   <-'   |             (x)
| | …
Run Code Online (Sandbox Code Playgroud)

c++ memory-model stdatomic c++20

5
推荐指数
1
解决办法
453
查看次数