我正在学习不同的记忆顺序。
\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}\nRun Code Online (Sandbox Code Playgroud)\n我认为如果它最终没有返回,则“获取”加载是不必要的from,那么“获取”负载是不必要的,因此我决定使用“宽松”负载,然后使用“获取”栅栏。
我期望它能工作,但它被线程清理程序拒绝了,线程清理程序声称并发state++是数据竞争。
#include <atomic>\n#include <iostream>\n#include <future>\n \nint state = 0;\nstd::atomic_int …Run Code Online (Sandbox Code Playgroud) 考虑下面的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
我目前正在阅读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)
Run Code Online (Sandbox Code Playgroud)Thread a (x is now true) Thread c (fails to increment z) Thread b (y …
根据 cppreference,std::atomic<T>::notify_one()将通知至少一个正在等待所述原子的线程。这意味着根据标准,它可以解锁多个线程。这与 相反std::condition_variable::notify_one(),后者指定它将解除阻塞(不超过)一个线程。
这种差异从何而来?这不使用相同的底层机制吗?就标准库的实现而言,所有流行的库是否都有机会通过此调用实际解锁多个,或者是否有一些库总是恰好解锁一个?
我有类似的东西:
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语句中的任何加载之前发生.
我在以下代码中有关于操作顺序的问题:
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在跑步thread1和thread2同时之后永远不会有结果.
但是,我在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++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?
当从原子函数指针调用函数时,例如:
#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 还指定了可能的候选调用:
operator __pointer_type() const noexceptoperator __pointer_type() const volatile noexcept看起来这种波动性的差异对于 clang 和 msvc 来说是令人困惑的,但对于 gcc 却不是。
f()当 call从改为 时f.load()(),代码可以在所有上述编译器中运行。这更令人困惑,因为据说load()和都有和重载 - 如果隐式转换不起作用,我预计也不会起作用。隐式转换(与成员调用)中的规则是否有所不同?operator T()constconst volatileload()
那么,gcc 接受该代码是错误的吗?clang和msvc错误会报错吗?还有其他错误或正确的组合吗?
这主要是一个理论问题,但如果有更好的方法来拥有原子函数指针,我想知道。
考虑以下两种变体:
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)
我从文档中看到第二个是完全原子的,但我不明白何时应该使用哪个以及什么是细节上的差异.