标签: stdatomic

为什么线程清理程序会抱怨获取/释放线程栅栏?

我正在学习不同的记忆顺序。

\n

我有这段代码,它可以工作并通过 GCC 和 Clang 的线程清理程序

\n
#include <atomic>\n#include <iostream>\n#include <future>\n    \nint state = 0;\nstd::atomic_int a = 0;\n\nvoid foo(int from, int to) \n{\n    for (int i = 0; i < 10; i++)\n    {\n        while (a.load(std::memory_order_acquire) != from) {}\n        state++;\n        a.store(to, std::memory_order_release);\n    }\n}\n\nint main()\n{    \n    auto x = std::async(std::launch::async, foo, 0, 1);\n    auto y = std::async(std::launch::async, foo, 1, 0);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我认为如果它最终没有返回,则“获取”加载是不必要的from,那么“获取”负载是不必要的,因此我决定使用“宽松”负载,然后使用“获取”栅栏。

\n

我期望它能工作,但它被线程清理程序拒绝了,线程清理程序声称并发state++是数据竞争。

\n
#include <atomic>\n#include <iostream>\n#include <future>\n    \nint state = 0;\nstd::atomic_int …
Run Code Online (Sandbox Code Playgroud)

c++ atomic memory-barriers stdatomic thread-sanitizer

21
推荐指数
1
解决办法
1244
查看次数

为什么不完全实现原子双重

我的问题很简单.为什么没有std::atomic<double> 完全实施?我知道它与互锁变量访问有关.但是我真的没有看到,为什么这不应该是双倍的.

它被指定可以使用任何普通的可复制类型.当然,双重是其中之一.所以基本操作应该没问题(设置,阅读等).但是,在整数上可能有一组额外的操作(fetch_add,++,+ =等).

双重与这些类型的差别很小.它是原生的,三维可复制的等等.为什么标准不包括这些类型的双重?

c++ floating-point atomic compare-and-swap stdatomic

20
推荐指数
2
解决办法
1万
查看次数

原子载荷可以在C++内存模型中合并吗?

考虑下面的C++ 11片段.对于GCC和clang,这会编译为两个(顺序一致的)foo.C++内存模型是否允许编译器将这两个加载合并到一个加载中并对x和y使用相同的值?

我认为它不能合并这些负载,因为这意味着轮询原子不再起作用,但我找不到内存模型文档中的相关部分.

#include <atomic>
#include <cstdio>

std::atomic<int> foo;

int main(int argc, char **argv)
{
    int x = foo;
    int y = foo;

    printf("%d %d\n", x, y);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

c++ memory-model compiler-optimization language-lawyer stdatomic

19
推荐指数
1
解决办法
464
查看次数

使用4个线程获取/释放语义

我目前正在阅读Anthony Williams的C++ Concurrency in Action.他的一个列表显示了这段代码,他声明z != 0可以解雇的断言.

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x()
{
    x.store(true,std::memory_order_release);
}

void write_y()
{
    y.store(true,std::memory_order_release);
}

void read_x_then_y()
{
    while(!x.load(std::memory_order_acquire));
    if(y.load(std::memory_order_acquire))
        ++z;
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));
    if(x.load(std::memory_order_acquire))
        ++z;
}

int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);
}
Run Code Online (Sandbox Code Playgroud)

所以我能想到的不同执行路径是这样的:

1)

Thread a (x is now true)
Thread c (fails to increment z)
Thread b (y …
Run Code Online (Sandbox Code Playgroud)

c++ multithreading memory-model memory-barriers stdatomic

19
推荐指数
2
解决办法
851
查看次数

std::atomic::notify_one 可以解锁多个线程

根据 cppreference,std::atomic<T>::notify_one()将通知至少一个正在等待所述原子的线程。这意味着根据标准,它可以解锁多个线程。这与 相反std::condition_variable::notify_one(),后者指定它将解除阻塞(不超过)一个线程。

这种差异从何而来?这不使用相同的底层机制吗?就标准库的实现而言,所有流行的库是否都有机会通过此调用实际解锁多个,或者是否有一些库总是恰好解锁一个?

c++ notify wait stdatomic c++20

19
推荐指数
2
解决办法
1004
查看次数

在x64上使用非临时存储获取/释放语义

我有类似的东西:

if (f = acquire_load() == ) {
   ... use Foo
}
Run Code Online (Sandbox Code Playgroud)

和:

auto f = new Foo();
release_store(f)
Run Code Online (Sandbox Code Playgroud)

您可以很容易地想象使用atomic with load(memory_order_acquire)和store(memory_order_release)的acquire_load和release_store的实现.但是现在如果release_store是用_mm_stream_si64实现的,这是一个非临时写入,而不是针对x64上的其他商店进行排序的?如何获得相同的语义?

我认为以下是最低要求:

atomic<Foo*> gFoo;

Foo* acquire_load() {
    return gFoo.load(memory_order_relaxed);
}

void release_store(Foo* f) {
   _mm_stream_si64(*(Foo**)&gFoo, f);
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

// thread 1
if (f = acquire_load() == ) {
   _mm_lfence(); 
   ... use Foo
}
Run Code Online (Sandbox Code Playgroud)

和:

// thread 2
auto f = new Foo();
_mm_sfence(); // ensures Foo is constructed by the time f is published to gFoo
release_store(f)
Run Code Online (Sandbox Code Playgroud)

那是对的吗?我非常肯定这里绝对需要sfence.但是那个lfence怎么样?是否需要或者简单的编译器障碍对于x64是否足够?例如asm volatile("":::"memory").根据x86内存模型,负载不会与其他负载重新排序.所以根据我的理解,只要存在编译器障碍,acquire_load()必须在if语句中的任何加载之前发生.

c++ multithreading x86-64 lock-free stdatomic

18
推荐指数
1
解决办法
1267
查看次数

C++内存模型中的哪些确切规则会阻止在获取操作之前重新排序?

我在以下代码中有关于操作顺序的问题:

std::atomic<int> x;
std::atomic<int> y;
int r1;
int r2;
void thread1() {
  y.exchange(1, std::memory_order_acq_rel);
  r1 = x.load(std::memory_order_relaxed);
}
void thread2() {
  x.exchange(1, std::memory_order_acq_rel);
  r2 = y.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)

鉴于std::memory_order_acquirecppreference页面上的描述(https://en.cppreference.com/w/cpp/atomic/memory_order),

具有此内存顺序的加载操作会对受影响的内存位置执行获取操作:在此加载之前,不能对当前线程中的读取或写入进行重新排序.

很明显,r1 == 0 && r2 == 0在跑步thread1thread2同时之后永远不会有结果.

但是,我在C++标准中找不到任何措辞(现在查看C++ 14草案),这保证了两个宽松的加载不能与获取 - 释放交换重新排序.我错过了什么?

编辑:正如评论中所建议的那样,实际上可以使r1和r2都等于零.我已经更新了程序以使用load-acquire,如下所示:

std::atomic<int> x;
std::atomic<int> y;
int r1;
int r2;
void thread1() {
  y.exchange(1, std::memory_order_acq_rel);
  r1 = x.load(std::memory_order_acquire);
}
void thread2() {
  x.exchange(1, std::memory_order_acq_rel);
  r2 = y.load(std::memory_order_acquire);
}
Run Code Online (Sandbox Code Playgroud)

现在是有可能得到两个和r1以及 …

c++ atomic memory-barriers language-lawyer stdatomic

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

为什么 std::atomic 构造函数在 C++14 和 C++17 中的行为不同

我在一个 C++11 项目中工作,我尝试了以下代码

#include <atomic>

struct A {
    std::atomic_int idx = 1;

};

int main() {
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我收到编译器错误

error: use of deleted function 'std::__atomic_base<_IntTp>::__atomic_base(const std::__atomic_base<_IntTp>&) [with _ITp = int]'
 std::atomic_int idx = 1;
                       ^
Run Code Online (Sandbox Code Playgroud)

C++14 也有同样的结果。当我切换到 C++17 时它可以工作:wandbox

我检查了 cppreference 的差异:

但是 C++14 和 C++17 之间没有区别。为什么它适用于 C++17 而不适用于 C++14?

c++ stdatomic c++14 c++17

18
推荐指数
1
解决办法
1108
查看次数

原子函数指针调用可以在 gcc 中编译,但不能在 clang 和 msvc 中编译

当从原子函数指针调用函数时,例如:

#include <atomic>
#include <type_traits>

int func0(){ return 0; }

using func_type = std::add_pointer<int()>::type;

std::atomic<func_type> f = { func0 };

int main(){
        f();
}
Run Code Online (Sandbox Code Playgroud)

gcc 根本不抱怨,而 clang 和 msvc 在调用方面有问题f()

  • [clang]:错误:对“std::atomic<func_type>”类型的对象的调用(又名“atomic<int (*)()>”)不明确
  • [msvc]:有不止一种方法可以为参数列表调用“std::atomic<func_type>”类型的对象

Clang 还指定了可能的候选调用:

  • operator __pointer_type() const noexcept
  • operator __pointer_type() const volatile noexcept

看起来这种波动性的差异对于 clang 和 msvc 来说是令人困惑的,但对于 gcc 却不是。

f()当 call从改为 时f.load()(),代码可以在所有上述编译器中运行。这更令人困惑,因为据说load()和都有和重载 - 如果隐式转换不起作用,我预计也不会起作用。隐式转换(与成员调用)中的规则是否有所不同?operator T()constconst volatileload()

那么,gcc 接受该代码是错误的吗?clang和msvc错误会报错吗?还有其他错误或正确的组合吗?


这主要是一个理论问题,但如果有更好的方法来拥有原子函数指针,我想知道。

c++ atomic language-lawyer stdatomic

18
推荐指数
1
解决办法
1082
查看次数

显式原子加载/存储和通常的运算符=和运​​算符T之间有什么区别?

考虑以下两种变体:

std::atomic<int> a;
a = 1;
int b = a;
Run Code Online (Sandbox Code Playgroud)

std::atomic<int> a;
a.store(1);
int b = a.load();
Run Code Online (Sandbox Code Playgroud)

我从文档中看到第二个是完全原子的,但我不明白何时应该使用哪个以及什么是细节上的差异.

c++ c++11 stdatomic

16
推荐指数
1
解决办法
2157
查看次数