gpt4 book ai didi

c# - 为什么此代码不产生撕裂读取?

转载 作者:太空狗 更新时间:2023-10-30 01:01:06 24 4
gpt4 key购买 nike

在查看 CLR/CLI 规范和内存模型等时,我注意到根据 ECMA CLI spec 围绕原子读/写的措辞:

A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size (the size of type native int) is atomic when all the write accesses to a location are the same size.

特别是短语“正确对齐的内存”引起了我的注意。我想知道我是否可以通过一些技巧在 64 位系统上使用 long 类型进行撕裂读取。所以我写了下面的测试用例:

unsafe class Program {
const int NUM_ITERATIONS = 200000000;
const long STARTING_VALUE = 0x100000000L + 123L;
const int NUM_LONGS = 200;
private static int prevLongWriteIndex = 0;

private static long* misalignedLongPtr = (long*) GetMisalignedHeapLongs(NUM_LONGS);

public static long SharedState {
get {
Thread.MemoryBarrier();
return misalignedLongPtr[prevLongWriteIndex % NUM_LONGS];
}
set {
var myIndex = Interlocked.Increment(ref prevLongWriteIndex) % NUM_LONGS;
misalignedLongPtr[myIndex] = value;
}
}

static unsafe void Main(string[] args) {
Thread writerThread = new Thread(WriterThreadEntry);
Thread readerThread = new Thread(ReaderThreadEntry);

writerThread.Start();
readerThread.Start();

writerThread.Join();
readerThread.Join();

Console.WriteLine("Done");
Console.ReadKey();
}

private static IntPtr GetMisalignedHeapLongs(int count) {
const int ALIGNMENT = 7;
IntPtr reservedMemory = Marshal.AllocHGlobal(new IntPtr(sizeof(long) * count + ALIGNMENT - 1));
long allocationOffset = (long) reservedMemory % ALIGNMENT;
if (allocationOffset == 0L) return reservedMemory;
return reservedMemory + (int) (ALIGNMENT - allocationOffset);
}

private static void WriterThreadEntry() {
for (int i = 0; i < NUM_ITERATIONS; ++i) {
SharedState = STARTING_VALUE + i;
}
}

private static void ReaderThreadEntry() {
for (int i = 0; i < NUM_ITERATIONS; ++i) {
var sharedStateLocal = SharedState;
if (sharedStateLocal < STARTING_VALUE) Console.WriteLine("Torn read detected: " + sharedStateLocal);
}
}
}

但是,无论我运行该程序多少次,我都没有合法地看到“检测到撕裂读取!”这一行。那为什么不呢?

我在单个 block 中分配了多个 long,希望其中至少有一个会溢出到两个缓存行之间;并且第一个 long 的“起点”应该未对齐(除非我误解了什么)。

我也知道多线程错误的性质意味着它们很难被强制执行,而且我的“测试程序”并不像它应该的那样严格,但我已经运行该程序将近 30 次了,没有结果 - 每个都有 200000000 次迭代。

最佳答案

这个程序中有许多隐藏撕裂读取的缺陷。对非同步线程的行为进行推理从来都不是简单的,也很难解释,意外同步的可能性总是很高。

  var myIndex = Interlocked.Increment(ref prevLongWriteIndex) % NUM_LONGS;

Interlocked 没有什么特别微妙的地方,不幸的是,它也对读者线程产生了很大影响。很难看到,但您可以使用 Stopwatch 为线程的执行计时。您会看到 Interlocked on the writer 会使读取器的速度减慢 ~2 倍。足以影响阅读者的时机而无法重现问题,意外同步。

消除危险并最大程度地提高检测到读取撕裂的几率的最简单方法是始终从同一内存位置读取和写入。修复:

  var myIndex = 0;

  if (sharedStateLocal < STARTING_VALUE)

此测试对检测撕裂读取没有太大帮助,有很多根本不会触发测试。 STARTING_VALUE 中有太多二进制零会使它变得更加不可能。最大化检测几率的一个很好的替代方法是在 1 和 -1 之间交替,确保字节值始终不同并使测试非常简单。因此:

private static void WriterThreadEntry() {
for (int i = 0; i < NUM_ITERATIONS; ++i) {
SharedState = 1;
SharedState = -1;
}
}

private static void ReaderThreadEntry() {
for (int i = 0; i < NUM_ITERATIONS; ++i) {
var sharedStateLocal = SharedState;
if (Math.Abs(sharedStateLocal) != 1) {
Console.WriteLine("Torn read detected: " + sharedStateLocal);
}
}
}

这很快就会让您在 32 位模式下的控制台中读取几页残缺的内容。要使它们也支持 64 位,您需要做额外的工作以使变量未对齐。它需要跨越 L1 高速缓存行边界,因此处理器必须执行两次读取和写入,就像在 32 位模式下一样。修复:

private static IntPtr GetMisalignedHeapLongs(int count) {
const int ALIGNMENT = -1;
IntPtr reservedMemory = Marshal.AllocHGlobal(new IntPtr(sizeof(long) * count + 64 + 15));
long cachelineStart = 64 * (((long)reservedMemory + 63) / 64);
long misalignedAddr = cachelineStart + ALIGNMENT;
if (misalignedAddr < (long)reservedMemory) misalignedAddr += 64;
return new IntPtr(misalignedAddr);
}

-1 和 -7 之间的任何 ALIGNMENT 值现在也会在 64 位模式下产生撕裂读取。

关于c# - 为什么此代码不产生撕裂读取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41657834/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com