标签: stdatomic

什么时候不应该使用 [[carries_dependency]]?

我发现了一些问题(比如这个)问什么[[carries_dependency]]是,这不是我在这里问的。

我想知道你什么时候不应该使用它,因为我读过的所有答案都让人觉得你可以把这段代码贴在任何地方,而且你会神奇地得到相等或更快的代码。一个评论说代码可以相等或更慢,但海报没有详细说明。

我想在任何函数返回或参数上使用 this 的合适位置是指针或引用,并且将在调用线程内传递或返回,并且不应在回调或线程入口点上使用它。

有人可以评论我的理解并详细说明一般的主题,何时以及何时不使用它?

编辑:我知道这个主题有这本书,如果其他读者感兴趣的话;它可能包含我的答案,但我还没有机会通读它。

c++ multithreading memory-model carries-dependency stdatomic

5
推荐指数
2
解决办法
269
查看次数

对 std::atomic::load 的结果使用 Structure dereference(->) 运算符是否安全

在尝试使用 std 原子指针时,我遇到了以下问题。说我这样做:

std::atomic<std::string*> myString;
// <do fancy stuff with the string... also on other threads>

//A can I do this?
myString.load()->size()

//B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);

//C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());
Run Code Online (Sandbox Code Playgroud)

我很确定 C 是非法的,因为在此期间 myString 可能会被删除。

但是我不确定 A 和 B。我认为它们是非法的,因为在执行读取操作时可能会尊重指针。

但是,如果是这种情况,您怎么能从可能被删除的原子指针中读取数据。由于负载为1步,读取数据为1步。

c++ atomic c++11 stdatomic

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

测量慢速原子增量与常规整数增量的比较

最近的一次讨论让我想知道与常规整数增量相比,原子增量有多昂贵.

我写了一些代码来尝试和基准测试:

#include <iostream>
#include <atomic>
#include <chrono>

static const int NUM_TEST_RUNS = 100000;
static const int ARRAY_SIZE = 500;

void runBenchmark(std::atomic<int>& atomic_count, int* count_array, int array_size, bool do_atomic_increment){    
    for(int i = 0; i < array_size; ++i){
        ++count_array[i];        
    }

    if(do_atomic_increment){
        ++atomic_count;
    }
}

int main(int argc, char* argv[]){

    int num_test_runs = NUM_TEST_RUNS;
    int array_size = ARRAY_SIZE;

    if(argc == 3){
        num_test_runs = atoi(argv[1]);
        array_size = atoi(argv[2]);        
    }

    if(num_test_runs == 0 || array_size == 0){
        std::cout << "Usage: atomic_operation_overhead <num_test_runs> <num_integers_in_array>" << …
Run Code Online (Sandbox Code Playgroud)

atomic c++11 stdatomic

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

混合松弛和释放-获取内存指令

考虑std::atomic<int> x(0)。如果我理解正确,则std::memory_order_relaxed仅保证操作以原子方式发生,但不提供同步保证。因此x.fetch_add(1, std::memory_order_relaxed),来自 2 个线程的 1000 次将始终具有 2000 的最终结果。但是,这些调用中的任何一个的返回值都不能保证反映真实的当前值(例如,第 2000 次增量可能返回 1700 作为前一个值)。

但是 - 这就是我的困惑 - 鉴于这些增量是并行发生的,会x.load(std::memory_order_acquire)返回什么?或者x.fetch_add(1, std::memory_order_acq_rel)?这些是否返回真实的当前值,或者它们是否具有由于宽松的增量而导致的宽松排序所具有的过时答案的相同问题?

据我所知,该标准仅保证释放到获取(在同一变量上)同步并因此给出真实的当前值。那么如何轻松混合典型的获取-释放语义呢?

例如,我听说std::shared_ptr的引用计数以宽松的顺序递增并以 acq_rel 的顺序递减,因为它需要确保它具有真实值以便只删除一次对象。因此,我很想认为他们会给出真实的当前值,但我似乎找不到任何标准来支持它。

c++ multithreading synchronization atomic stdatomic

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

原子操作传播/可见性(原子负载与原子 RMW 负载)

语境

我正在用 C++编写一个线程安全的原型线程/协程库,并且我正在使用原子来使任务切换无锁。我希望它尽可能高效。我对原子和无锁编程有一个大致的了解,但我没有足够的专业知识来优化我的代码。我做了很多研究,但很难找到我的具体问题的答案:不同内存顺序下不同原子操作的传播延迟/可见性是多少?

当前假设

我读到对内存的更改是从其他线程传播的,它们可能会变得可见:

  1. 以不同的顺序给不同的观察者,
  2. 一些延迟。

我不确定这种延迟的可见性和不一致的传播是仅适用于非原子读取,还是也适用于原子读取,这可能取决于使用的内存顺序。当我在 x86 机器上开发时,我无法在弱有序系统上测试行为。

无论操作类型和使用的内存顺序如何,所有原子读取都总是读取最新值吗?

我很确定所有读-修改-写(RMW) 操作总是读取任何线程写入的最新值,而不管使用的内存顺序如何。对于顺序一致的操作似乎也是如此,但前提是对变量的所有其他修改也是顺序一致的。据说两者都很慢,这对我的任务不利。如果不是所有原子读取都获得最新值,那么我将不得不使用 RMW 操作来读取原子变量的最新值,或者在 while 循环中使用原子读取,以我目前的理解。

写入的传播(忽略副作用)是否取决于内存顺序和使用的原子操作?

(仅当上一个问题的答案是并非所有原子读取总是读取最新值时,此问题才有意义。请仔细阅读,我在这里不询问副作用的可见性和传播。我只关心原子变量本身的值。)这意味着根据用于修改原子变量的操作,可以保证任何后续原子读取接收变量的最新值。因此,我必须在保证始终读取最新值的操作之间进行选择,或者使用宽松的原子读取,以及这种特殊的写入操作,以保证对其他原子操作的修改的即时可见性。

c++ multithreading propagation memory-visibility stdatomic

5
推荐指数
2
解决办法
780
查看次数

在 ARM 上加载和存储重新排序

我不是 ARM 专家,但至少在某些 ARM 架构上不会对这些存储和加载进行重新排序吗?

  atomic<int> atomic_var; 
  int nonAtomic_var;
  int nonAtomic_var2;

  void foo()
  {       
          atomic_var.store(111, memory_order_relaxed);
          atomic_var.store(222, memory_order_relaxed);
  }

  void bar()
  {       
          nonAtomic_var = atomic_var.load(memory_order_relaxed);
          nonAtomic_var2 = atomic_var.load(memory_order_relaxed);
  }
Run Code Online (Sandbox Code Playgroud)

我没有成功地让编译器在它们之间放置内存屏障。

我试过如下(在 x64 上):

$ arm-linux-gnueabi-g++ -mcpu=cortex-a9 -std=c++11 -S -O1 test.cpp
Run Code Online (Sandbox Code Playgroud)

我有:

_Z3foov:
          .fnstart
  .LFB331:
          @ args = 0, pretend = 0, frame = 0
          @ frame_needed = 0, uses_anonymous_args = 0
          @ link register save eliminated.
          movw    r3, #:lower16:.LANCHOR0
          movt    r3, #:upper16:.LANCHOR0
          mov     r2, #111
          str     r2, [r3]
          mov     r2, …
Run Code Online (Sandbox Code Playgroud)

c++ arm memory-model memory-barriers stdatomic

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

与普通变量相比,仅读取原子变量是否有任何性能差异?

int i = 0;
if(i == 10)  {...}  // [1]

std::atomic<int> ai{0};
if(ai == 10) {...}  // [2]
if(ai.load(std::memory_order_relaxed) == 10) {...}  // [3]
Run Code Online (Sandbox Code Playgroud)

在多线程环境中,语句 [1] 是否比语句 [2] & [3] 更快?
假设ai在执行 [2] 和 [3] 时,可能会或可能不会在另一个线程中写入。

附加:如果不需要底层整数的准确值,那么读取原子变量的最快方法是什么?

c++ performance multithreading atomic stdatomic

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

C++ 标准如何使用 memory_order_acquire 和 memory_order_release 防止自旋锁互斥锁中的死锁?

TL:DR:如果互斥体实现使用获取和释放操作,那么实现是否可以像通常允许的那样进行编译时重新排序,并重叠两个应该独立于不同锁的临界区?这将导致潜在的僵局。


假设互斥锁在 上实现std::atomic_flag

struct mutex
{
   void lock() 
   {
       while (lock.test_and_set(std::memory_order_acquire)) 
       {
          yield_execution();
       }
   }

   void unlock()
   {
       lock.clear(std::memory_order_release);
   }

   std::atomic_flag lock; // = ATOMIC_FLAG_INIT in pre-C++20
};
Run Code Online (Sandbox Code Playgroud)

到目前为止看起来还可以,关于使用单个这样的互斥锁:std::memory_order_releasestd::memory_order_acquire.

在这里使用std::memory_order_acquire/std::memory_order_release不应该一见钟情。它们类似于 cppreference 示例https://en.cppreference.com/w/cpp/atomic/atomic_flag

现在有两个互斥锁保护不同的变量,两个线程以不同的顺序访问它们:

mutex m1;
data  v1;

mutex m2;
data  v2;

void threadA()
{
    m1.lock();
    v1.use();
    m1.unlock();

    m2.lock();
    v2.use();
    m2.unlock();
}

void threadB()
{
    m2.lock();
    v2.use();
    m2.unlock();

    m1.lock();
    v1.use();
    m1.unlock();
}
Run Code Online (Sandbox Code Playgroud)

释放操作可以在无关的获取操作之后重新排序(无关操作 == 对不同对象的后续操作),因此执行可以转换如下:

mutex m1;
data  v1;

mutex …
Run Code Online (Sandbox Code Playgroud)

c++ memory-model spinlock language-lawyer stdatomic

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

Intel x86 上是否需要内存排序:consume、acq_rel 和 seq_cst?

C++11 规定了六种内存顺序:

typedef enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
} memory_order;
Run Code Online (Sandbox Code Playgroud)

https://en.cppreference.com/w/cpp/atomic/memory_order

其中默认值为 seq_cst。

可以通过放宽操作的内存顺序来获得性能提升。但是,这取决于体系结构提供的保护。例如,Intel x86 是一种强大的内存模型,可以保证各种加载/存储组合不会被重新排序。

因此relaxedacquirerelease似乎是寻求在x86额外的性能时所需的唯一排序。

这样对吗?如果没有,是否需要在 x86 上使用consume,acq_relseq_cst

c++ optimization performance x86 stdatomic

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

memory_order_consume 到底有什么作用?

来自链接: 加载/存储宽松原子变量和普通变量有什么区别?

这个回答给我留下了深刻的印象:

使用原子变量解决了这个问题——通过使用原子,即使内存顺序放宽,所有线程都可以保证读取最新的写入值。

今天,我阅读了以下链接:https : //preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/

atomic<int*> Guard(nullptr);
int Payload = 0;
Run Code Online (Sandbox Code Playgroud)

线程1:

  Payload = 42;
    Guard.store(&Payload, memory_order_release);
Run Code Online (Sandbox Code Playgroud)

线程2:

g = Guard.load(memory_order_consume);
if (g != nullptr)
    p = *g;
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

问题: 我了解到数据依赖会阻止相关指令被重新排序。但我认为这对于确保执行结果的正确性是显而易见的。comsume-release 语义是否存在并不重要。所以我想知道 comsum-release 真的可以。哦,也许它使用数据依赖性来防止指令重新排序,同时确保 Payload 的可见性

所以

如果我使 1.preventing 指令重新排序 2.确保 Payload 的非原子变量的可见性,是否有可能使用 memory_order_relaxed 获得相同的正确结果:

atomic<int*> Guard(nullptr);
volatile int Payload = 0;   // 1.Payload is volatile now

// 2.Payload.assign and Guard.store in order for data dependency
Payload = 42;               
Guard.store(&Payload, memory_order_release);

// 3.data Dependency …
Run Code Online (Sandbox Code Playgroud)

c++ cpu-architecture memory-model lock-free stdatomic

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