我想大致这样做:
初始线程:
std::atomic<>。其他线程:
现在,我知道我可以将参数传递给std::thread,但我试图通过这个例子来理解 C++ 的内存保证。
此外,我非常有信心,在任何现实世界的实现中,创建线程都会导致内存障碍,确保线程可以“看到”父线程在此之前编写的所有内容。
但我的问题是:这是由标准保证的吗?
旁白:我想我可以添加一些虚拟的东西std::atomic<int>,并在启动其他线程之前写入,然后在其他线程上,在启动时读取一次。我相信所有的happens-before 机制都可以保证先前编写的全局状态是正确可见的。
但我的问题是,技术上是否需要这样的东西,或者线程创建是否足够?
我目前正在阅读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 …
我在《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) 我试图了解std::atomic_thread_fence(std::memory_order_seq_cst);栅栏的用途,以及它们与栅栏有何不同acq_rel。
到目前为止,我的理解是,唯一的区别是 seq-cst 栅栏影响 seq-cst 操作的全局顺序 ( [atomics.order]/4)。并且只有在实际执行 seq-cst 加载时才能观察到所述顺序。
所以我想,如果我没有 seq-cst 负载,那么我可以用 acq-rel 栅栏替换所有 seq-cst 栅栏,而不改变行为。那是对的吗?
如果这是正确的,为什么我会看到这样的代码“使用栅栏实现 Dekker 算法”,它使用 seq-cst 栅栏,同时保持所有原子读/写宽松?这是该博客文章中的代码:
std::atomic<bool> flag0(false),flag1(false);
std::atomic<int> turn(0);
void p0()
{
flag0.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
while (flag1.load(std::memory_order_relaxed))
{
if (turn.load(std::memory_order_relaxed) != 0)
{
flag0.store(false,std::memory_order_relaxed);
while (turn.load(std::memory_order_relaxed) != 0)
{
}
flag0.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
}
}
std::atomic_thread_fence(std::memory_order_acquire);
// critical section
turn.store(1,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
flag0.store(false,std::memory_order_relaxed);
}
void p1()
{
flag1.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
while (flag0.load(std::memory_order_relaxed))
{
if (turn.load(std::memory_order_relaxed) != 1)
{
flag1.store(false,std::memory_order_relaxed);
while …Run Code Online (Sandbox Code Playgroud) 我正在阅读《C++ Concurrency in Action》第二版。下面的代码来自清单 7.6。它pop()使用危险指针来实现堆栈。
std::shared_ptr<T> pop() {
std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();
node* old_head = head.load(); // #1
do {
node* temp;
do {
temp = old_head;
hp.store(old_head); // #2
old_head = head.load(); // #3
} while (old_head != temp); // #4
} while (old_head &&
!head.compare_exchange_strong(old_head, old_head->next));
hp.store(nullptr);
// ...
}
Run Code Online (Sandbox Code Playgroud)
书中解释了内循环的作用:
您必须在
while循环中执行此操作,以确保node在读取旧head指针#1和设置危险指针#2之间没有删除。在此窗口期间,没有其他线程知道您正在访问该特定节点。幸运的是,如果旧head节点要被删除,head那么它本身一定已经发生了变化,因此您可以检查这一点并继续循环,直到您知道该head指针仍然具有与您设置危险指针相同的值#4。
根据 的实现,如果另一个线程在和之间pop删除了头节点,则将被修改为新节点。pop …
与我之前的问题类似,请考虑以下代码
-- Initially --
std::atomic<int> x{0};
std::atomic<int> y{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
y.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire); // x first
int r2 = y.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = y.load(std::memory_order_acquire); // y first
int r4 = x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
是怪异的结果 r1==1, r2==0,并r3==2, r4==0有可能在C ++ 11内存模型下,这种情况下?如果我要全部替换std::memory_order_acq_rel成该std::memory_order_relaxed怎么办?
在x86上,这样的结果似乎是被禁止的,请参见此SO问题,但我一般是在询问C ++ 11内存模型。
奖励问题:
我们都同意,与std::memory_order_seq_cst该怪异的结果不会在C ++ 11被允许。现在,赫伯·萨特(Herb Sutter)在他著名的- …