众所周知,只要重新排序对代码语义没有区别,Java语言允许编译器重新排列编译代码行.但是,编译器只需要打扰从当前线程看到的sematics .如果此重新排序影响多线程情况下的语义,则通常会导致并发问题(内存可见性)
我的问题:
通过将这个freedm允许编译器实现了什么?编译器是否真的可以通过重新排列代码来生成更高效的代码?我还没有看到一个实际案例.我觉得有时可以带来的好处远远超过这可能带来的并发风险.
程序员有没有办法告诉编译器不要重新排列这样的行?我知道使用同步原语有效地处理重新排列的副作用,但我问是否有任何直接的方法(编译器选项)来关闭它?
当且仅当所有顺序一致的执行没有数据争用时,程序才能正确同步.
它只给我们定义"顺序一致",它没有给我们定义"顺序一致的执行".只有在知道什么是"顺序一致的执行"之后,我们才可以进一步讨论该主题.
什么是"顺序一致的执行"以及什么是"顺序一致的执行没有数据竞争"?
根据文档,Java在并发包中提供了一个Lock对象 provides more extensive locking operations than can be obtained using synchronized methods and statements.
除了互斥之外的同步方法/块,强制执行先发生关系,这确保一个线程对变量的更改对另一个线程可见.
使用Lock对象时是否会出现这种关系?对于所有平台,观察是否保证像同步块一样?
假设我有2个线程:
int value = 0;
std::atomic<bool> ready = false;
thread 1:
value = 1
ready = true;
thread 2:
while (!ready);
std::cout << value;
Run Code Online (Sandbox Code Playgroud)
这个程序能输出0吗?
我读到了有关C ++内存模型的信息-具体来说,是顺序一致性,我认为这是默认设置,但并不是特别清楚。是仅要求编译器相对于彼此以正确的顺序放置原子操作,还是相对于所有其他操作以正确的顺序放置原子操作?
public class TestHashRace
{
private int cachedHash = 0;
private readonly object value;
public object Value
{
get { return value; }
}
public TestHashRace(object value)
{
this.value = value;
}
public override int GetHashCode()
{
if (cachedHash == 0) {
cachedHash = value.GetHashCode();
}
return cachedHash;
}
//Equals isn't part of the question, but since comments request it, here we go:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() …Run Code Online (Sandbox Code Playgroud) 显然,顺序一致的原子操作的有效可观察行为与有效C++程序中的仅获取释放操作不同.定义在C++标准(自C++ 11以来)或此处给出.
但是,我从来没有遇到过一个算法或数据结构的真实例子,其中获取 - 释放语义不足并且需要顺序一致性.
什么是真实世界算法或数据结构的实际例子,其中需要顺序一致性并且获取 - 释放内存顺序是不够的?
注意,即使std::mutex不保证顺序一致性.
我编写了一个简单的“信封”类,以确保我正确理解C ++ 11原子语义。我有一个标头和一个有效负载,编写器清除该标头,填充有效负载,然后用递增的整数填充标头。这样的想法是,读取器然后可以读取标头,将有效负载换出,再次读取标头,如果标头相同,则读取器可以假定他们成功复制了有效负载。读者可能会错过一些更新是可以的,但是让他们获得更新的撕裂(其中来自不同更新的字节混合在一起)也不是可以的。永远只有一个读者和一个作家。
编写者使用释放内存顺序,而读者使用获取内存顺序。
是否存在通过原子存储/加载调用对memcpy重新排序的风险?还是可以将负载彼此重新排序?这永远不会让我流产,但也许我很幸运。
#include <iostream>
#include <atomic>
#include <thread>
#include <cstring>
struct envelope {
alignas(64) uint64_t writer_sequence_number = 1;
std::atomic<uint64_t> sequence_number;
char payload[5000];
void start_writing()
{
sequence_number.store(0, std::memory_order::memory_order_release);
}
void publish()
{
sequence_number.store(++writer_sequence_number, std::memory_order::memory_order_release);
}
bool try_copy(char* copy)
{
auto before = sequence_number.load(std::memory_order::memory_order_acquire);
if(!before) {
return false;
}
::memcpy(copy, payload, 5000);
auto after = sequence_number.load(std::memory_order::memory_order_acquire);
return before == after;
}
};
envelope g_envelope;
void reader_thread()
{
char local_copy[5000];
unsigned messages_received = 0;
while(true) {
if(g_envelope.try_copy(local_copy)) {
for(int i …Run Code Online (Sandbox Code Playgroud) 我认为 C++ 还没有涵盖任何类型的事务内存,但 TSX 仍然可以以某种方式将“好像规则”用于由 C++ 内存模型管理的东西。
那么,成功的 HLE 操作或成功的 RTM 事务会发生什么?
说“存在数据竞争,但没关系”并没有多大帮助,因为它没有阐明“正常”的含义。
使用 HLE 可能可以将其视为“前一个操作发生在后续操作之前。好像该部分仍然由被省略的锁保护”。
RTM 是什么?由于甚至没有省略锁,只有(可能是非原子的)内存操作,可能是加载、存储、两者或无操作。什么与什么同步?在什么之前会发生什么?
共享内存多处理系统通常需要为缓存一致性生成大量流量。核心 A 写入缓存。Core B 稍后可能会读取相同的内存位置。因此,内核 A,即使它本来可以避免写入主内存,也需要向内核 B 发送通知,告诉 B 如果该地址正在缓存中,则该地址无效。
究竟什么时候需要这样做,这是一个复杂的问题。不同的 CPU 架构有不同的内存模型,这里上下文中的内存模型是一组关于观察到的事情发生的顺序的保证。内存模型越弱,A 在发送通知的确切时间就越放松对于 B,A 和 B 更容易并行做更多的事情。不同 CPU 架构的内存模型总结:https : //en.wikipedia.org/wiki/Memory_ordering#Runtime_memory_ordering
所有的讨论似乎是关于当失效发生时,什么为了事情发生英寸
但在我看来,在许多工作负载中,A 写入的大部分数据永远不会被 B 使用,因此如果可以完全消除那些缓存失效的总线流量会更好。专用于执行缓存一致性的硬件仍然需要存在,因为 A 和 B 有时需要共享数据,但写入共享总线是 CPU 可以做的更耗能的事情之一,并且电池寿命和散热通常是现在限制资源,因此减少总线流量将是一个有用的优化。有没有办法做到这一点?
从效率的角度来看,理想的情况是如果忽略总线流量是默认的(因为大多数写入的数据不与其他线程共享),并且您必须在需要缓存一致性的地方显式地发出内存屏障。另一方面,这可能是不可能的,因为假设它在 x86 或 ARM 上运行的现有代码量很大;有没有办法反过来,向 CPU 指示给定的缓存行永远不会对任何其他线程感兴趣?
我会对任何系统的答案感兴趣,但最特别是 x64、ARM 或 RISC-V 上 Linux 最常见的当前/未来服务器配置。
multithreading cpu-architecture memory-model memory-barriers cpu-cache
ARMv8.3 引入了新指令:LDAPR。
当 STLR 后跟 LDAR 到不同的地址时,这两个不能重新排序,因此称为 RCsc(释放一致顺序一致)。
当 STLR 后跟 LDAPR 到不同的地址时,这 2 个地址可以重新排序。这称为RCpc(发布一致处理器一致)。
我的问题是PC部分。
PC 是 TSO 的松弛,其中 TSO 是多副本原子,而 PC 是非多副本原子。
ARMv8的内存模型已改进为多副本原子,因为没有供应商创建过非多副本原子微体系结构,这使得内存模型更加复杂。
所以我遇到了矛盾。
关键问题是:每个存储(包括宽松的存储)都是多副本原子的吗?
如果是这样,那么 rcpc 的 PC 部分对我来说没有意义,因为 PC 是非多副本原子的。由于 ARM 过去是非多副本原子的,它是否可能是一个遗留名称?
PC有多种定义;所以也许这就是原因。