JLS有两个结论:
data-race-free => sequentially consistentcorrectly synchronized => sequentially consistent如果C1的反面是真的,那么我们可以得出结论:
correctly synchronized => data-race-free但不幸的是,JLS中没有这样的陈述,所以我得出了第四个结论:
但我对这种方法并不满意,并且想要证明这个结论是正确的(或错误的),即使是以非正式的方式或以样本的方式.
首先,我认为显示包含数据竞争的多线程程序的顺序一致执行的代码段有助于理解和解决此问题.
经过认真考虑,我仍然找不到合适的样品.那么请你给我这样的代码段吗?
我在Anthony Williams的书"C++ Concurrency"的内存模型中测试了这个例子
#include<atomic>
#include<thread>
#include<cassert>
std::atomic_bool x,y;
std::atomic_int z;
void write_x_then_y() {
x.store(true, std::memory_order_relaxed);
y.store(true, std::memory_order_relaxed);
}
void read_y_then_x() {
while(!y.load(std::memory_order_relaxed));
if(x.load(std::memory_order_relaxed)) {
++z;
}
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0);
}
Run Code Online (Sandbox Code Playgroud)
根据解释,对差异变量(此处为x和y)的放松操作可以自由重新排序.但是,我重复运行这个问题超过几天.我从未遇到断言(assert(z.load()!= 0);)触发的情况.我只是使用默认优化并使用g ++编译代码-std = c ++ 11 -lpthread dataRaceAtomic.cpp有没有人真正尝试并点击断言?谁能给我一个关于我测试结果的解释?顺便说一句,我也尝试了不使用原子类型的版本,我得到了相同的结果.目前,这两个项目都在健康运行.谢谢.
循环提升易失性读数
我已经阅读过很多地方,一个volatile变量不能从循环中提升或者如果,但我找不到这提到C#规范中的任何地方.这是隐藏的功能吗?
所有写入在C#中都是易失的
这是否意味着所有写入都没有相同的属性,就像使用volatile关键字一样?例如,C#中的普通写入具有发布语义?并且所有写入都会刷新处理器的存储缓冲区?
释放语义
这是一种正式的方式,说明当完成易失性写入时处理器的存储缓冲区被清空了吗?
获取语义
这是一种正式的说法是不应该将变量加载到寄存器中,而是每次从内存中获取它吗?
在本文中,Igoro谈到"线程缓存".我完全明白这是想象的,但他实际上指的是:
或者这只是我的想象力?
延迟写作
我读过许多写作可以延迟的地方.这是因为重新排序和存储缓冲区吗?
Memory.Barrier
我知道副作用是在JIT将IL转换为asm时调用"lock or",这就是为什么Memory.Barrier可以解决fx这个例子中对主内存(在while循环中)的延迟写入的原因:
static void Main()
{
bool complete = false;
var t = new Thread (() =>
{
bool toggle = false;
while (!complete) toggle = !toggle;
});
t.Start();
Thread.Sleep (1000);
complete = true;
t.Join(); // Blocks indefinitely
}
Run Code Online (Sandbox Code Playgroud)
但情况总是这样吗?对Memory.Barrier的调用是否总是刷新存储缓冲区,将更新的值提取到处理器缓存中?我知道完整的变量不会被提升到寄存器中,而是每次都从处理器缓存中提取,但由于对Memory.Barrier的调用,处理器缓存会更新.
我在这里的冰上,还是我对volatile和Memory.Barrier的某种理解?
在以下简单场景中:
class A {
int x;
Object lock;
...
public void method(){
synchronized(lock){
// modify/read x and act upon its value
}
}
}
Run Code Online (Sandbox Code Playgroud)
x需要变化吗?我知道同步保证原子性,但我不确定可见性虽然...确实锁定 - >修改 - >解锁 - >锁定保证,在第二次锁定后,x的值将是"新鲜"?
我有一个程序会产生多个线程,这些线程可能会将完全相同的值写入完全相同的内存位置:
std::vector<int> vec(32, 1); // Initialize vec with 32 times 1
std::vector<std::thread> threads;
for (int i = 0 ; i < 8 ; ++i) {
threads.emplace_back([&vec]() {
for (std::size_t j = 0 ; j < vec.size() ; ++j) {
vec[j] = 0;
}
});
}
for (auto& thrd: threads) {
thrd.join();
}
Run Code Online (Sandbox Code Playgroud)
在此简化的代码中,所有线程都可以尝试将完全相同的值写入中的相同内存位置vec。这是一场数据竞赛可能会触发未定义的行为,还是安全的,因为在再次连接所有线程之前从不读取值?
如果存在潜在的危险数据竞争,那么使用std::vector<std::atomic<int>>带std::memory_order_relaxed存储的替代方法足以防止数据竞争吗?
与我之前的问题类似,请考虑以下代码
-- 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)在他著名的- …
我看到g ++生成一个简单的movfor x.load()和mov+ mfencefor x.store(y).考虑这个经典的例子:
#include<atomic>
#include<thread>
std::atomic<bool> x,y;
bool r1;
bool r2;
void go1(){
x.store(true);
}
void go2(){
y.store(true);
}
bool go3(){
bool a=x.load();
bool b=y.load();
r1 = a && !b;
}
bool go4(){
bool b=y.load();
bool a=x.load();
r2= b && !a;
}
int main() {
std::thread t1(go1);
std::thread t2(go2);
std::thread t3(go3);
std::thread t4(go4);
t1.join();
t2.join();
t3.join();
t4.join();
return r1*2 + r2;
}
Run Code Online (Sandbox Code Playgroud)
其中根据https://godbolt.org/z/APS4ZY go1和go2被翻译成
go1():
mov BYTE PTR x[rip], 1 …Run Code Online (Sandbox Code Playgroud) C ++标准的当前草案(2019年3月)具有以下段落([basic.types] p.4)(重点是我的):
类型T的对象的对象表示形式是类型T的对象所占用的N个无符号字符对象的序列,其中N等于sizeof(T)。类型T的对象的值表示形式是参与表示类型T的值的位集合。对象表示形式中不属于值表示形式的位是填充位。 对于普通可复制类型,值表示形式是对象表示形式中确定值的一组位,该值是实现定义的一组值中的一个离散元素。
为什么突出显示的句子仅限于平凡可复制的类型?是因为不可平凡对象的值表示形式中的某些位可能不在其对象表示形式之外?这个答案,以及这个暗示。
但是,在上面链接的答案中,对象的概念价值基于用户引入的语义。在第一个链接的答案的示例中:
class some_other_type
{
int a;
std::string s;
};
Run Code Online (Sandbox Code Playgroud)
用户确定类型对象的值some_other_type包括属于string的字符s。
我试图考虑一些示例,其中某个对象的值表示(不容易复制)的某些位不在其对象表示之内的事实是隐式的(实现必须这样做,它不是由用户任意确定的)。
我想到的一个例子是,使用虚拟方法的基类子对象的值表示形式可能包含其所属完整对象的对象表示形式中的位,因为基类子对象的行为可能有所不同(可能“具有一个不同的值”),相比之下,它本身就是一个完整的对象。
尽管我想到的另一个例子是,vtable也可能是其vtable指针指向它的对象的值表示的一部分。
这些例子正确吗?还有其他例子吗?
是标准委员会引入突出显示的句子,是因为对象的语义“值”可能由用户决定(如在两个链接的答案中一样),或者由于实现方式可能决定(或可能是强制执行此操作,或两者都执行?
谢谢。
我正在检查编译器如何为x86_64上的多核内存屏障发出指令。以下代码是我正在测试的代码gcc_x86_64_8.3。
std::atomic<bool> flag {false};
int any_value {0};
void set()
{
any_value = 10;
flag.store(true, std::memory_order_release);
}
void get()
{
while (!flag.load(std::memory_order_acquire));
assert(any_value == 10);
}
int main()
{
std::thread a {set};
get();
a.join();
}
Run Code Online (Sandbox Code Playgroud)
使用时std::memory_order_seq_cst,我可以看到该MFENCE指令用于任何优化-O1, -O2, -O3。该指令确保刷新了存储缓冲区,因此在L1D缓存中更新了它们的数据(并使用MESI协议确保其他线程可以看到效果)。
但是,当我std::memory_order_release/acquire不进行优化MFENCE使用时,也会使用指令,但是使用-O1, -O2, -O3优化会忽略该指令,并且不会看到其他刷新缓冲区的指令。
在MFENCE不使用的情况下,如何确保将存储缓冲区数据提交给高速缓存以确保内存顺序语义?
以下是使用get / set函数的汇编代码-O3,例如我们在Godbolt编译器资源管理器中获得的代码:
set():
mov DWORD PTR any_value[rip], 10
mov BYTE PTR flag[rip], 1
ret
.LC0:
.string …Run Code Online (Sandbox Code Playgroud) 在最简单的示例中,假设我有一个启动线程的函数,该函数依次将局部变量的值设置为true。我们加入线程,然后离开函数。
bool func() {
bool b = false;
std::thread t([&]() { b = true; });
t.join();
return b;
}
Run Code Online (Sandbox Code Playgroud)
该函数将返回true,还是行为未定义?