我有以下代码:
#include <atomic>
int main () {
std::atomic<uint32_t> value(0);
value.fetch_add(1, std::memory_order::relaxed);
static_assert(std::atomic<uint32_t>::is_always_lock_free);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
它可以编译,所以这意味着std::atomic<uint32_t>::is_always_lock_free是真的。
然后,使用 gcc 10 和 的汇编代码如下所示-std=c++20 -O3 -mtune=skylake-avx512 -march=skylake-avx512:
0000000000401050 <main>:
401050: c7 44 24 fc 00 00 00 mov DWORD PTR [rsp-0x4],0x0
401057: 00
401058: f0 ff 44 24 fc lock inc DWORD PTR [rsp-0x4]
40105d: 31 c0 xor eax,eax
40105f: c3 ret
Run Code Online (Sandbox Code Playgroud)
许多帖子指出,读-修改-写操作(fetch_add()此处)不能是没有锁的原子操作。
我的问题是std::atomic::is_always_lock_free存在的true真正含义是什么。
该页面说明Equals true if this atomic type is …
我在《C++ Concurrency in Action》一书中遇到了这段代码,用于简单实现屏障(对于不能std::experimental::barrier在 C++17 或std::barrierC++20 中使用的代码)。
[编辑]屏障是一种同步机制,其中一组线程(线程数传递给屏障的构造函数)可以到达并等待(通过调用 wait 方法)或到达并丢弃(通过调用 did_waiting) 。如果组中的所有线程都到达屏障,则屏障将被重置,并且线程可以继续执行下一组操作。如果组中的某些线程脱落,则组中的线程数量相应减少,以进行下一轮与屏障的同步。[编辑结束]
以下是为简单实现屏障而提供的代码。
struct barrier
{
std::atomic<unsigned> count;
std::atomic<unsigned> spaces;
std::atomic<unsigned> generation;
barrier(unsigned count_):count(count_),spaces(count_),generation(0)
{}
void wait(){
unsigned const gen=generation.load();
if(!--spaces){
spaces=count.load();
++generation;
}else{
while(generation.load()==gen){
std::this_thread::yield();
}
}
}
void done_waiting(){
--count;
if(!--spaces){
spaces=count.load();
++generation;
}
}
};
Run Code Online (Sandbox Code Playgroud)
作者 Anthony Williams 提到,他选择顺序一致性排序是为了更容易推理代码,并表示可以使用宽松的排序来提高代码效率。这就是我更改代码以采用宽松排序的方法。请帮助我理解我的代码是否正确。
struct barrier
{
std::atomic<unsigned> count;
std::atomic<unsigned> spaces;
std::atomic<unsigned> generation;
barrier(unsigned count_):count(count_),spaces(count_),generation(0)
{}
void wait(){
unsigned const gen=generation.load(std::memory_order_acquire);
if(1 == spaces.fetch_sub(1, std::memory_order_relaxed)){
spaces=count.load(std::memory_order_relaxed);
generation.fetch_add(1, std::memory_order_release); …Run Code Online (Sandbox Code Playgroud) 我不确定我是否完全理解C ++ 11中原子性和内存排序的概念(我可能全都错了)。让我们以这个简单的单线程示例为例:
int main()
{
std::atomic<int> a(0);
std::atomic<int> b(0);
a.store(16);
b.store(10);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在此单线程代码中,如果a和b不是原子类型,则编译器可能以某种方式对指令进行了重新排序,例如,在汇编代码中,例如,我有一条将10赋给'b'的move指令,然后将将16分配给“ a”。因此,对我来说,作为原子变量,它可以确保我在源代码中声明的“ b移动指令”之前拥有“移动指令”。之后,处理器将带有执行单元,预取指令以及乱序框。无论汇编代码中的指令顺序如何,该处理器都可以在“ a指令”之前处理“ b指令”。
以我的理解,这就是内存排序模型出现的地方。从那时起,如果我让默认模型顺序一致。一个可以保证我清除主内存中的这些值(10和16)将遵守我在源代码中存储的顺序。这样,处理器将开始清空寄存器或高速缓存,其中将16存储到主存储器中以进行更新'a',然后处理器将清空主存储器中的10'b'。
这样的行为的确使我了解到,如果我使用宽松的内存模型。只有最后一部分不能保证,因此主内存中的刷新可能完全混乱。
抱歉,如果您在阅读我时遇到麻烦,我的英语仍然很差。但是谢谢你们的时间。
根据这个https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html,已发布的商店MOV在x86(包括x86-64)上实现为(进入内存).
根据他的http://en.cppreference.com/w/cpp/atomic/memory_order
memory_order_release:
具有此内存顺序的存储操作将执行释放操作:在此存储之后,不能对当前线程中的内存访问进行重新排序.这确保了当前线程中的所有写入在获取或相同原子变量的其他线程中可见,并且带有依赖关系到原子变量的写入在消耗相同原子的其他线程中变得可见.
我知道当使用memory_order_release时,之前完成的所有内存存储应该在此之前完成.
int a;
a = 10;
std::atomic<int> b;
b.store(50, std::memory_order_release); // i can be sure that 'a' is already 10, so processor can't reorder the stores to 'a' and 'b'
Run Code Online (Sandbox Code Playgroud)
问题:MOV对于这种行为,裸指令(没有明确的内存栅栏)是否足够?如何MOV告诉处理器完成以前的所有商店?
我std::thread在我的C++代码中使用一个不断轮询一些数据并将其添加到缓冲区.我使用a C++ lambda来启动这样的线程:
StartMyThread() {
thread_running = true;
the_thread = std::thread { [this] {
while(thread_running) {
GetData();
}
}};
}
Run Code Online (Sandbox Code Playgroud)
thread_running 是atomic<bool>在类头中声明的.这是我的GetData功能:
GetData() {
//Some heavy logic
}
Run Code Online (Sandbox Code Playgroud)
接下来我还有一个StopMyThread函数,我将其设置thread_running为false,以便它退出while中的while循环lambda block.
StopMyThread() {
thread_running = false;
the_thread.join();
}
Run Code Online (Sandbox Code Playgroud)
据我了解,我可以暂停和使用恢复该线程std::condition_variable的指出这里在我前面的问题.
但是,如果我只是使用std::atomic<bool> thread_running执行或不执行GetData()下面的逻辑,是否有缺点?
GetData() {
if (thread_running == false)
return;
//Some heavy logic
}
Run Code Online (Sandbox Code Playgroud)
与使用 …
我知道在线程2中的发布存储操作和线程1中的获取加载操作之间将发生同步关系,即使该加载操作不直接读取线程2存储的值,前提是存在发布存储操作与实际读取的存储之间的"释放顺序",只要:
但是,我没有看到任何理由为什么在实际读取的存储位于不同的线程中时也不可能同步,前提是发布存储操作仍然发生 - 在存储之前实际上正在阅读.标准明确不允许这样做吗?如果是这样,那么标准是否可能不完整是因为它有意义并且所有现有硬件都会有这样的同步?
考虑以下示例,其中a,x和y是使用0初始化的原子int.
线程1:
k = y.load(memory_order_acquire);
x.store(1, memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)
线程2:
m = x.load(memory_order_relaxed);
y.store(2, memory_order_release);
a.store(2, memory_order_release);
Run Code Online (Sandbox Code Playgroud)
线程3:
n = a.load(memory_order_acquire);
y.store(3, memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)
问题是,有可能我们最终得到k = 3,m = 1和n = 2?
如果线程2中的存储到y和线程3中的存储到y之间没有释放序列,那么在线程2中的释放存储到y之间没有同步 - 并且在线程1中没有与y的获取读取,因此线程2中的x的负载不必在线程1中存储到x之前发生,使得k,m和n的期望结果成为可能.
但是,如果有是商店至y在线程2和存储到y在螺纹3之间的释放顺序则是一个进行同步-与释放存储到y在线程2和获取在线程1读出用Y之间因此,线程2中x的负载需要在线程1中存储到x之前发生,从而无法获得所需的k,m和n结果.请注意,如果a的存储/加载不在那里,我们只是在线程2的末尾做了值为3到y的松弛存储,那么情况就是如此(所以k = 3和m =永远不会发生1).
在这种情况下,值3到y的存储发生在线程3中,但是存在使用原子变量a的释放 - 获取同步; 因此,如果n = 2,那么在值2到y的释放存储和值3到y的松弛存储之间存在先发生关系.并不意味着存在是一个释放顺序和结果,其中k = 3,M = 1且n = 2永远不会发生?
编辑
请注意,运行以下代码段:
int main()
{
atomic_int a = 0;
atomic_int x = 0;
atomic_int y = 0; …Run Code Online (Sandbox Code Playgroud) 我不知道如何创建以下内容:
std::pair<std::atomic<bool>, int>
Run Code Online (Sandbox Code Playgroud)
我总是总是得到
/usr/include/c++/5.5.0/bits/stl_pair.h:139:45:错误:使用已删除的功能'std :: atomic :: atomic(const std :: atomic&)'
:第一(__x),第二(std :: forward <_U2>(__ y)){}
我试过了
std::pair<std::atomic<bool>, int> pair = std::make_pair(true, 1); //doesn't work
std::pair<std::atomic<bool>, int> pair = std::make_pair({true}, 1); //doesn't work
std::pair<std::atomic<bool>, int> pair = std::make_pair(std::atomic<bool>(true), 1); //doesn't work
std::pair<std::atomic<bool>, int> pair = std::make_pair(std::move(std::atomic<bool>(true)), 1); //doesn't work
Run Code Online (Sandbox Code Playgroud)
我知道std :: atomic是不可复制的,那么您应该如何成对创建它?只是不可能吗?
我感觉这可能是一种非常普遍和常见的情况,对此有一个众所周知的无锁解决方案。
简而言之,我希望有一种像读取器/写入器锁这样的方法,但这不需要读取器获取锁,因此可以获得更好的平均性能。
相反,对于读取器来说会有一些原子操作(128 位 CAS),对于写入器来说会有一个互斥锁。我有数据结构的两个副本,一个用于正常成功查询的只读副本,以及一个要在互斥锁保护下更新的相同副本。将数据插入可写副本后,我们将其设为新的可读副本。一旦所有待处理的读取器都读完它,旧的可读副本就会被依次插入,写入器会旋转剩余的读取器数量,直到其为零,然后依次修改它,最后释放互斥体。
或类似的东西。
存在这样的东西吗?
在 C++20 中,我们可以这样写:
double x;
double x_value = std::atomic_ref(x).load();
Run Code Online (Sandbox Code Playgroud)
有没有同样效果的功能?
我试过,std::atomic_load但似乎没有非原子对象的重载。
考虑下面的示例是打算等到另一个线程存储42在一个共享变量shared没有锁,无需等待线程终止,为什么会volatile T或std::atomic<T>会要求或建议,以保证并发正确性?
#include <atomic>
#include <cassert>
#include <cstdint>
#include <thread>
int main()
{
int64_t shared = 0;
std::thread thread([&shared]() {
shared = 42;
});
while (shared != 42) {
}
assert(shared == 42);
thread.join();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 GCC 4.8.5 和默认选项,示例按预期工作。