在没有易失性机制的情况下,CPU 什么时候写入主内存?
When does a CPU write to main memory in the absence of a volatile mechanism?
在 x64 或 ARM 上的 multi-core/multiprocessor 环境 运行 中考虑以下 C# 代码:
public sealed class Trio
{
public long A;
public long B;
public long C;
}
public static class MP
{
private static readonly object locker = new object();
private static readonly Trio Data = new Trio();
public static Trio ReadCopy()
{
lock (locker)
{
return new Trio { A = Data.A, B = Data.B, C = Data.C };
}
}
public static void Set(long a, long b, long c)
{
lock (locker)
{
Data.A = a;
Data.B = b;
Data.C = c;
}
}
}
线程的同步显然处理的很清楚。
但是,根据我的理解,我有一个问题基于以下观察:
-
lock
语句保证 a) 只有一个线程可以访问 Data
和 b) Data
中的字段永远不会是 "torn".
Lock
提供了一个内存屏障,据我所知,它在这两个上下文中不会产生任何明显的影响。
- 由于字段没有标记
volatile
并且没有Volatile.Read()
和Volatile.Write()
操作,所以这三个字段将写入缓存,而不是直接写入主存。
- 直接写入主内存的唯一方法是通过上述 "volatile" 机制之一,因为这些机制使用
ref
操作并禁用优化,从而导致主内存 read/write。
- 查看代码,CPU 将在我不知道的某个时间点将这些字段写入主内存。
- 我不明白为什么多个线程可以保证看到三个字段的最新版本,尤其是在 ARM 这样的弱序内存架构上。
我的问题是:如何确定在调用 Set()
之后调用 ReadCopy()
会看到这三个字段的最新值?调用线程可以在不同的核心上,并有自己的 Data
.
缓存副本
"Volatile" 机制显然是有原因的。该示例通常围绕访问非锁定内存段展开。但是,这里的例子呢?我从未见过使用 lock
和 使用可变机制的代码。
引用 Igor Ostrovsky 的文章 C# - The C# Memory Model in Theory and Practice:
When a locked block of code executes, it’s guaranteed to see all writes from blocks that precede the block in the sequential order of the lock. Also, it’s guaranteed not to see any of the writes from blocks that follow it in the sequential order of the lock.
In short, locks hide all of the unpredictability and complexity weirdness of the memory model: You don’t have to worry about the reordering of memory operations if you use locks correctly.
我认为这很彻底地回答了你的问题!
还有第二部分:C# - The C# Memory Model in Theory and Practice, Part 2
在 x64 或 ARM 上的 multi-core/multiprocessor 环境 运行 中考虑以下 C# 代码:
public sealed class Trio
{
public long A;
public long B;
public long C;
}
public static class MP
{
private static readonly object locker = new object();
private static readonly Trio Data = new Trio();
public static Trio ReadCopy()
{
lock (locker)
{
return new Trio { A = Data.A, B = Data.B, C = Data.C };
}
}
public static void Set(long a, long b, long c)
{
lock (locker)
{
Data.A = a;
Data.B = b;
Data.C = c;
}
}
}
线程的同步显然处理的很清楚。
但是,根据我的理解,我有一个问题基于以下观察:
-
lock
语句保证 a) 只有一个线程可以访问Data
和 b)Data
中的字段永远不会是 "torn". Lock
提供了一个内存屏障,据我所知,它在这两个上下文中不会产生任何明显的影响。- 由于字段没有标记
volatile
并且没有Volatile.Read()
和Volatile.Write()
操作,所以这三个字段将写入缓存,而不是直接写入主存。 - 直接写入主内存的唯一方法是通过上述 "volatile" 机制之一,因为这些机制使用
ref
操作并禁用优化,从而导致主内存 read/write。 - 查看代码,CPU 将在我不知道的某个时间点将这些字段写入主内存。
- 我不明白为什么多个线程可以保证看到三个字段的最新版本,尤其是在 ARM 这样的弱序内存架构上。
我的问题是:如何确定在调用 Set()
之后调用 ReadCopy()
会看到这三个字段的最新值?调用线程可以在不同的核心上,并有自己的 Data
.
"Volatile" 机制显然是有原因的。该示例通常围绕访问非锁定内存段展开。但是,这里的例子呢?我从未见过使用 lock
和 使用可变机制的代码。
引用 Igor Ostrovsky 的文章 C# - The C# Memory Model in Theory and Practice:
When a locked block of code executes, it’s guaranteed to see all writes from blocks that precede the block in the sequential order of the lock. Also, it’s guaranteed not to see any of the writes from blocks that follow it in the sequential order of the lock.
In short, locks hide all of the unpredictability and complexity weirdness of the memory model: You don’t have to worry about the reordering of memory operations if you use locks correctly.
我认为这很彻底地回答了你的问题!
还有第二部分:C# - The C# Memory Model in Theory and Practice, Part 2