考虑以下简单函数(假设大多数编译器优化关闭)由具有存储缓冲区的 X86 CPU 上不同内核上的两个线程执行:
struct ABC
{
int x;
//other members.
};
void dummy(int index)
{
while(true)
{
auto abc = new ABC;
abc->x = index;
cout << abc->x;
// do some other things.
delete abc;
}
}
Run Code Online (Sandbox Code Playgroud)
这里,index是线程的索引;1 由线程1 传递,2 由线程2 传递。因此,线程 1 应该始终打印 1,线程 2 应该始终打印 2。
是否存在这样的情况,即存储到x被放入存储缓冲区并在执行delete之后提交?或者是否存在隐式内存屏障来确保在删除之前提交存储?或者一旦遇到删除,任何未完成的存储都会被丢弃?
这变得很重要的情况:
由于delete是将对象的内存返回到空闲列表中(用libc),因此有可能在thread1中刚刚释放的一块内存被thread2中的new操作符返回(不仅是虚拟地址,甚至是返回的底层物理地址可以是相同的)。如果未完成的存储可以在删除后执行,则线程 2 将 abc->x 设置为 2 后,线程 1 中的某些较旧的未完成存储可能会将其覆盖为 1。
这意味着在上面的程序中,thread2可以打印1,这是绝对错误的。线程1和线程2是完全独立的,从程序员的角度来看,线程之间没有数据共享,并且它们不必担心任何同步。
我在这里缺少什么?
在gcc中,我们可以使用asm volatile("":::"memory");
但我在 rust inline asm 文档中找不到类似于“内存”的选项。
有什么办法可以做到这一点吗?
考虑以下简单的 Java 应用程序:
public class Main {
public int a;
public volatile int b;
public void thread1(){
int b;
a = 1;
b = this.b;
}
public void thread2(){
int a;
b = 1;
a = this.a;
}
public static void main(String[] args) throws Exception {
Main m = new Main();
while(true){
m.a = 0;
m.b = 0;
Thread t1 = new Thread(() -> m.thread1());
Thread t2 = new Thread(() -> m.thread2());
t1.start();
t2.start();
t1.join();
t2.join();
}
}
}
Run Code Online (Sandbox Code Playgroud)
问题: …
std::atomic<bool> x{ false };
std::atomic<bool> y{ false };
// thread 1
y.store(true, std::memory_order_seq_cst);
x.store(true, std::memory_order_release);
// thread2
while (!x.load(std::memory_order_relaxed);
assert(y.load(std::memory_order_seq_cst)); // !!!
Run Code Online (Sandbox Code Playgroud)
断言会失败吗?我的理解是:虽然读取x是“放松的”,但一旦“线程2”看到“线程1”的写入,它就看不到y,false因为写入y发生在写入之前x。
内存顺序是从现实生活中的案例复制的,对于这个示例来说可能会更弱,但我没有改变它,以免错过任何微妙之处。
我现在正在阅读有关内存屏障和障碍的信息,以此来同步多线程代码并避免代码重新排序.
我通常在Linux OS下用C++开发,我boost大量使用libs,但是我找不到任何与之相关的类.你知道在增强中是否存在栅栏的记忆障碍,或者是否有办法实现相同的概念?如果不是我可以看看哪个好的图书馆?
给定一个struct数组:
public struct Instrument
{
public double NoS;
public double Last;
}
var a1 = new Instrument[100];
Run Code Online (Sandbox Code Playgroud)
并且一个线程任务池正在写入这些元素,因为单个元素最多可以同时由两个线程写入,每个双字段一个(有效地通过主题进行上游排队).
并且知道双重可以在64位上原子地写入.(编辑这个错误地说原来是32位)
我需要使用数组中的所有值定期执行计算,并且我希望它们在计算期间保持一致.
所以我可以使用以下方法对数组进
var snapshot = a1.Clone();
Run Code Online (Sandbox Code Playgroud)
现在我的问题是关于同步的具体细节.如果我使成员易变,我认为这根本不会帮助克隆,因为读/写aquire/releases不在数组级别.
现在我可以有一个数组锁,但这会在最常见的数据写入数组的过程中引起很多争论.所以不太理想.
或者我可以有一个每行锁定,但这将是一个真正的痛苦,因为他们都需要在克隆之前被获取,同时我有所有备份的写入.
现在,如果快照没有最新值(如果它只是微秒等)并不重要,所以我想我可能会因为没有锁而逃脱.我唯一担心的是,是否存在持续时间段内没有缓存写回的情况.这是我应该担心的吗?编写器在TPL数据流中,唯一的逻辑是在结构中设置两个字段.我真的不知道函数范围是如何或者是否与缓存回写相关联.
思考/建议吗?
编辑:如果我使用互锁写入结构中的变量怎么样?
edit2:写入量比读取量高很多.写入Nos&Last字段还有两个单独和并发的服务.所以他们可以同时写入.这导致参考对象方法存在原子性问题.
edit3:更多细节.假设数组是30-1000个元素,每个元素可以每秒多次更新.
我查看了TI C/C++编译器v6.1用户指南(spru514e),但没有找到任何内容.
该asm声明在这方面似乎没有提供任何内容,手册甚至警告不要改变变量值(p132).未实现用于声明对变量的影响的GNU扩展(p115).
我也没有找到任何记忆障碍的内在因素(比如__memory_changed()Keil的armcc).
搜索网络或TI论坛也一无所获.
任何其他提示如何进行?
(我知道他们没有,但我正在寻找其实际工作的根本原因,而不使用volatile,因为没有什么可以防止编译器将变量存储在没有volatile的寄存器中......或者是......)
这个问题源于思想的不和谐,即没有易失性的编译器(理论上可以通过各种方式优化任何变量,包括将其存储在CPU寄存器中.)虽然文档说在使用锁定变量等同步时不需要.但实际上在某些情况下,编译器/ jit似乎无法知道您是否会在代码路径中使用它们.所以怀疑是在这里真正发生的事情,使记忆模型"工作".
在这个例子中,什么阻止编译器/ jit优化_count到一个寄存器,从而在寄存器上完成增量而不是直接到存储器(稍后在退出调用后写入内存)?如果_count是易变的,那么看起来一切都应该没问题,但很多代码都是在没有volatile的情况下编写的.如果在方法中看到锁定或同步对象,编译器可能知道不会将_count优化到寄存器中,但在这种情况下,锁定调用是在另一个函数中.
大多数文档都说如果使用锁等同步调用,则不需要使用volatile.
那么是什么阻止了编译器优化_count到寄存器并可能只更新锁中的寄存器?我有一种感觉,大多数成员变量都不会因为这个原因被优化到寄存器中,因为每个成员变量都需要是易变的,除非编译器告诉它不应该优化(否则我怀疑大量的代码会失败) .我看到类似的东西,看看C++多年前本地函数变量存储在寄存器中,类成员变量没有.
所以主要的问题是,它是否真的是没有volatile的唯一方法,编译器/ jit不会将类成员变量放在寄存器中,因此不需要volatile?
(请忽略调用中缺少异常处理和安全性,但你得到了要点.)
public class MyClass
{
object _o=new object();
int _count=0;
public void Increment()
{
Enter();
// ... many usages of count here...
count++;
Exit();
}
//lets pretend these functions are too big to inline and even call other methods
// that actually make the monitor call (for example a base class that implemented these)
private void Enter() { Monitor.Enter(_o); }
private void Exit() { Monitor.Exit(_o); } //lets pretend this function …Run Code Online (Sandbox Code Playgroud) 我已经发现,在x86 CPU有以下内存屏障指令:mfence,lfence,和sfence。
x86 CPU是否仅具有这三个内存屏障指令,或者还有更多指令?
该mfence 文件说以下内容:
对MFENCE指令之前发出的所有内存加载和存储到内存指令执行序列化操作.此序列化操作保证在遵循MFENCE指令的任何加载或存储指令之前,按程序顺序在MFENCE指令之前的每个加载和存储指令都变为全局可见.
据我所知,x86中没有fence指令可以防止非读取和非写入指令的重新排序.
现在如果我的程序只有一个线程,即使指令被重新排序,它仍然看起来好像指令正在按顺序执行.
但是,如果我的程序有多个线程,并且在其中一个线程中非读取和非写入指令被重新排序,其他线程会注意到这个重新排序(我假设答案是否定,否则会有一个fence指令停止非读取和非写入指令重新排序,或者我可能缺少某些东西)?