所以我现在研究这个主题很长一段时间了,我想我理解最重要的概念,如发布和获取内存栅栏.
但是,我还没有找到令人满意的解释,volatile主存储器的缓存和缓存之间的关系.
因此,我理解对volatile字段的每次读写操作都会对读取以及之前和之后的写入操作执行严格的排序(读取 - 获取和写入 - 释放).但这只能保证操作的顺序.它没有说明这些更改对其他线程/处理器可见的时间.特别是,这取决于刷新缓存的时间(如果有的话).我记得曾读过Eric Lippert的评论,他说" volatile字段的存在会自动禁用缓存优化".但我不确定这究竟是什么意思.这是否意味着整个程序的缓存完全被禁用,因为我们在volatile某处有一个字段?如果不是,禁用缓存的粒度是多少?
此外,我读到了一些关于强和弱的易失性语义的东西,并且C#遵循强大的语义,即每次写入都将直接进入主存储器,无论它是否是一个volatile字段.我对这一切感到非常困惑.
当我在一个字段中写入一个值时,在将新值保存在主内存中时,我能得到什么保证?例如,我怎么知道处理器没有将新值保留在它的私有缓存中,而是更新了主内存?
另一个例子:
int m_foo;
void Read() // executed by thread X (on processor #0)
{
Console.Write(m_foo);
}
void Write() // executed by thread Y (on processor #1)
{
m_foo = 1;
}
Run Code Online (Sandbox Code Playgroud)
是否有可能在Write()完成执行后,其他一些线程执行Read()但实际上会看到"0"作为当前值?(因为之前对m_foo的写入可能还没有刷新?).
什么样的原语(除了锁)可用于确保写入被刷新?
编辑
在我使用的代码示例中,写入和读取以不同的方法放置.Thread.MemoryBarrier不会影响同一范围内存在的指令重写吗?
另外,假设它们不会被JIT内联,我怎样才能确保写入m_foo的值不会存储在寄存器中,而是存储在主存中?(或者,当读取m_foo时,它不会从CPU缓存中获取旧值).
是否可以在不使用锁或'volatile'关键字的情况下实现此目的?(另外,假设我没有使用原始类型,但是WORD大小的结构[不能应用那么易变].)
普通加载在x86上获得了语义,普通商店具有发布语义,但编译器仍然可以重新排序指令.虽然围栏和锁定指令(锁定的xchg,锁定的cmpxchg)会阻止硬件和编译器重新排序,但仍需要普通的加载和存储来保护编译器障碍.Visual C++提供了_ReadWriterBarrier()函数,它可以防止编译器重新排序,同样C++提供volatile关键字也是出于同样的原因.我写这些信息只是为了确保我把一切都弄好.所以上面写的都是真的,有没有理由将其标记为将在_ReadWriteBarrier()保护的函数中使用的volatile变量?
例如:
int load(int& var)
{
_ReadWriteBarrier();
T value = var;
_ReadWriteBarrier();
return value;
}
Run Code Online (Sandbox Code Playgroud)
使变量非易失性是否安全?据我所知,因为函数受到保护,内部编译器无法进行重新排序.另一方面,Visual C++为volatile变量提供了特殊的行为(不同于标准的变量),它使得volatile可以读写原子加载和存储,但是我的目标是x86,而且x86上的普通加载和存储应该是原子的无论如何,对吗?
提前致谢.
我最近一直在尝试使用Electric Fence,我无法弄清楚如何将它与c ++代码一起使用.
这是一个例子:
// test.cpp
#include <cstdlib>
using namespace std;
int main()
{
int *a = new int(10);
delete a;
}
Run Code Online (Sandbox Code Playgroud)
我编译了它
g++ ./test.cpp -o test -lefence -L/home/bor/efence_x86_64/lib -lpthread
Run Code Online (Sandbox Code Playgroud)
而且我在开始时看不到Electric Fence标题,并且在可执行文件中找不到EF符号(使用nm命令).
但是,如果我修改这样的程序:
// test.cpp
#include <cstdlib>
using namespace std;
int main()
{
char *p = (char*)malloc(20);
free(p);
int *a = new int(10);
delete a;
}
Run Code Online (Sandbox Code Playgroud)
一切都很好 - EF出现了.我知道它解决了这个问题,我知道:).我只是想了解为什么它首先不起作用,因为new()应该打电话malloc(),delete()打电话free(),不是吗?
我参与其中的原因是一个使用boost库和其他几个的大项目.这个程序从不打电话malloc()或free()直接打电话.当我使用EF构建它时,我不会将EF链接到最终的可执行文件,但重建了所有试图将EF链接到它们的库.我无法在其中任何一个中找到EF符号.这是正确的方法吗?或者它是错误的,EF最终只能链接到可执行文件,libs应保持不变?但是我再次在可执行文件中找不到EF符号.
有人可以验证我对构造函数执行后建立的内存栅栏的理解.例如,假设我有一个名为Stock的类.
public final class Stock{
private final String ticker;
private double qty;
private double price;
public Stock ( String ticker, double qty, double price ){
this.ticker = ticker;
this.qty = qty;
this.price = price;
//I am assuming a memory fence gets inserted here.
}
public final void updateQty( double qty ){
this.qty = qty;
}
public final void updatePrice( double price ){
this.price = price;
}
}
Run Code Online (Sandbox Code Playgroud)
此外,假设构造是由执行线程1,然后updateQty()和updatePrice()被称为若干时间线程2(总是由线程2).
我的论点是,在Thread1创建对象之后,对象的"可见性"与jvm中的所有其他线程建立.由于两个可变变量仅由Thread2更改,因此我不需要任何锁定.我对么?
正如我们从之前的回答中所知道的,它是否在处理器x86/x86_64中指示LFENCE?我们不能使用SFENCE而不是MFENCE顺序一致性.
这里的答案表明MFENCE= SFENCE+ LFENCE,即LFENCE没有我们不能提供顺序一致性的东西.
LFENCE 无法重新排序:
SFENCE
LFENCE
MOV reg, [addr]
Run Code Online (Sandbox Code Playgroud)
- 到 - >
MOV reg, [addr]
SFENCE
LFENCE
Run Code Online (Sandbox Code Playgroud)
例如重新排序MOV [addr], reg LFENCE- > LFENCE MOV [addr], reg由机制提供- 存储缓冲区,它重新排序存储 - 负载以提高性能,并且因为LFENCE它不会阻止它.并SFENCE 禁用此机制.
什么机制禁用LFENCE无法重新排序(x86没有机制 - Invalidate-Queue)?
并且只是在理论上或者在现实中重新排序SFENCE MOV reg, [addr]- > MOV reg, [addr] SFENCE可能吗?如果可能,实际上,什么机制,它是如何工作的?
我有std::atomic<int>* key, *val;
我想写两个.有多个线程同时读取这些值.我想确保在key之前写入val.这确保了如果读者看到key的新值,他们还必须看到val的新值.可以使用旧密钥查看新的val,或查看两个旧值.代码必须适用于宽松的处理器体系结构(ARM).
通常,它(按顺序)就足够了(按顺序)val->store(x, relaxed), key->store(y, release)加载它们key->load(acquire), val-load(relaxed).在ARM上,我认为收购插入一个负载屏障,释放插入一个商店屏障.但是,由于各种原因,我没有使用原子来读取val,并且在每次读取访问时使用屏障会太昂贵.
是否可以在两个商店之间仅使用商店屏障(x86上的sfence)来强制订购?记住看到key和val的过时值非常好,我想要确保的是,如果key的新值被另一个核看到,它还必须看到val的新值.读取器总是首先读取密钥,而val的读取取决于密钥的值(即使它的内存地址是),所以我不相信编译器或处理器可以重新排序负载,这样val就是在钥匙前读.它必须读取密钥甚至知道在哪里寻找val.我怀疑是这个属性允许代码在没有负载障碍的情况下工作.
我对吗?
我对在Java中的线程之间安全地共享数组感到困惑,特别是内存栅栏和关键字synchronized.
这个Q&A很有帮助,但没有回答我的所有问题:Java数组:synchronized + Atomic*,还是同步的?
以下是演示该问题的示例代码.假设有一个工作线程池填充了SharedTablevia方法add(...).完成所有工作线程后,最后一个线程将读取并保存数据.
用于演示此问题的示例代码:
public final class SharedTable {
// Column-oriented data entries
private final String[] data1Arr;
private final int[] data2Arr;
private final long[] data3Arr;
private final AtomicInteger nextIndex;
public SharedTable(int size) {
this.data1Arr = new String[size];
this.data2Arr = new int[size];
this.data3Arr = new long[size];
this.nextIndex = new AtomicInteger(0);
}
// Thread-safe: Called by worker threads
public void addEntry(String data1, int data2, long data3) {
final int index = nextIndex.getAndIncrement();
data1Arr[index] …Run Code Online (Sandbox Code Playgroud) 假设我们有一个内存区域,某个线程正在其中写入数据。然后它将注意力转向其他地方并允许任意其他线程读取数据。然而,在某个时间点,它想要重用该内存区域并再次写入。
编写器线程提供一个布尔标志(valid),它指示内存仍然可以有效读取(即,他还没有重用它)。在某些时候,他会将此标志设置为 false,并且永远不会再次将其设置为 true(它只会翻转一次,仅此而已)。
考虑到顺序一致性,对于作者和读者来说,分别使用这两个代码片段应该是正确的:
...
valid = false;
<write to shared memory>
...
Run Code Online (Sandbox Code Playgroud)
和
...
<read from shared memory>
if (valid) {
<be happy and work with data read>
} else {
<be sad and do something else>
}
...
Run Code Online (Sandbox Code Playgroud)
显然我们需要做一些事情来确保顺序一致性,即插入必要的获取和释放内存屏障。在将任何数据写入段之前,我们希望在写入线程中将该标志设置为 false 。我们希望在检查之前在读取器线程中从内存中读取数据valid。后者是因为我们知道 valid 是单调的,即如果之后它仍然有效则读时也是有效的。
在内存访问和访问之间插入一个完整的栅栏valid就可以了。然而,我想知道,制造valid一个原子是否就足够了?
std::atomic<bool> valid = true;
Run Code Online (Sandbox Code Playgroud)
然后
...
valid.store(false); // RELEASE
<write to shared memory>
...
Run Code Online (Sandbox Code Playgroud)
和
...
<read …Run Code Online (Sandbox Code Playgroud) 在x86上,除了原子操作之外,还提供了lock诸如lock cmpxchg提供屏障语义之类的前缀指令:对于回写内存区域的正常内存访问,读取和写入不是lock按照第3卷第8.2.2节中的预定指令重新排序的英特尔SDM:
无法使用I/O指令,锁定指令或序列化指令对读取或写入进行重新排序.
本节仅适用于回写内存类型.在同一个列表中,您会发现一个例外情况,它指出没有订购弱排序的商店:
- 读取不会与其他读取重新排序.
- 写入不会与较旧的读取重新排序.
- 写入内存不会与其他写入重新排序,但以下情况除外: -
使用非时间移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS和MOVNTPD)执行的流存储(写入); 而且 -
字符串操作(参见第8.2.4.1节).
注意,列表中的任何其他项目中的非时间指令没有例外,例如,在涉及锁定前缀指令的项目中.
在本指南的各种其他部分中,提到当使用弱有序(非时间)指令时,mfence和/或sfence指令可用于命令存储器.这些部分通常不提及lock- 作为替代的前缀指令.
所有这一切都让我不确定:do lock-prefixed指令提供了相同的完整屏障,它mfence提供了WB内存上的弱有序(非时间)指令之间的?同样的问题再次适用于WC内存的任何类型的访问.
memory-fences ×10
c++ ×4
volatile ×3
atomic ×2
c# ×2
java ×2
lock-free ×2
memory-model ×2
x86 ×2
arrays ×1
assembly ×1
caching ×1
concurrency ×1
constructor ×1
intel ×1
linux ×1
memory ×1
mutex ×1
visual-c++ ×1
x86-64 ×1