gpt4 book ai didi

C#/CLR : MemoryBarrier and torn reads

转载 作者:太空狗 更新时间:2023-10-29 23:39:23 25 4
gpt4 key购买 nike

只是在业余时间玩并发性,并想尝试在不使用读取器端锁定的情况下防止撕裂读取,以便并发读取器不会相互干扰。

这个想法是通过锁序列化写入,但在读取端仅使用内存屏障。这是一个可重用的抽象,它封装了我提出的方法:

public struct Sync<T>
where T : struct
{
object write;
T value;
int version; // incremented with each write

public static Sync<T> Create()
{
return new Sync<T> { write = new object() };
}

public T Read()
{
// if version after read == version before read, no concurrent write
T x;
int old;
do
{
// loop until version number is even = no write in progress
do
{
old = version;
if (0 == (old & 0x01)) break;
Thread.MemoryBarrier();
} while (true);
x = value;
// barrier ensures read of 'version' avoids cached value
Thread.MemoryBarrier();
} while (version != old);
return x;
}

public void Write(T value)
{
// locks are full barriers
lock (write)
{
++version; // ++version odd: write in progress
this.value = value;
// ensure writes complete before last increment
Thread.MemoryBarrier();
++version; // ++version even: write complete
}
}
}

不要担心版本变量溢出,我避免了另一种方式。那么我上面对 Thread.MemoryBarrier 的理解和应用是否正确呢?是否有任何障碍是不必要的?

最佳答案

我仔细查看了你的代码,它对我来说似乎是正确的。一件事立即让我感到震惊的是,您使用了一种既定的模式来执行低锁定操作。我可以看到您正在使用 version作为一种虚拟锁。偶数被释放,奇数被获取。并且由于您对虚拟锁使用了单调递增的值,因此您也避免了 ABA problem .然而,最重要的是,您在尝试读取时继续循环,直到观察到虚拟锁值在读取开始前与读取完成后相同。否则,您会认为这是一次失败的读取并重新尝试。所以,是的,核心逻辑的工作做得很好。

那么内存屏障生成器的位置呢?嗯,这一切看起来也不错。所有的Thread.MemoryBarrier需要调用。如果我不得不挑剔,我会说您需要在 Write 中添加一个方法,使其看起来像这样。

public void Write(T value)
{
// locks are full barriers
lock (write)
{
++version; // ++version odd: write in progress
Thread.MemoryBarrier();
this.value = value;
Thread.MemoryBarrier();
++version; // ++version even: write complete
}
}

此处添加的调用确保 ++versionthis.value = value不要被交换。现在,ECMA 规范在技术上允许这种指令重新排序。但是,Microsoft 的 CLI 和 x86 硬件的实现都已经在写入时具有 volatile 语义,因此在大多数情况下实际上并不需要它。但是,谁知道呢,也许在针对 ARM cpu 的 Mono 运行时上可能有必要。

关于 Read一边的事情我也找不出什么毛病。事实上,您所拥有的调用的位置正是我将它们放置的位置。有些人可能想知道为什么在最初阅读 version 之前不需要一个.原因是因为 Thread.MemoryBarrier,当第一次读取被缓存时,外循环会捕获这种情况。再向下。

所以这让我开始讨论性能。这真的比在 Read 中使用硬锁更快吗?方法?好吧,我对你的代码做了一些非常广泛的测试来帮助回答这个问题。答案是肯定的!这比使用硬锁要快得多。我使用 Guid 进行了测试作为值类型,因为它是 128 位,因此比我机器的 native 字大小(64 位)大。我还在作者和读者的数量上使用了几种不同的变体。您的低锁技术始终且显着优于硬锁技术。我什至使用 Interlocked.CompareExchange 尝试了一些变体做守卫阅读,他们也都变慢了。事实上,在某些情况下,它实际上比使用硬锁更慢。我必须诚实。我对此一点也不感到惊讶。

我还做了一些非常重要的有效性测试。我创建了可以运行相当长一段时间的测试,而且我一次也没有看到撕裂的阅读。然后作为对照测试,我会调整 Read以这样一种方式,我知道它是不正确的,我再次运行了测试。这一次,正如预期的那样,撕裂的读数开始随机出现。我将代码切换回您拥有的代码,撕裂的读数消失了;再次,正如预期的那样。这似乎证实了我已经预料到的。也就是说,您的代码看起来是正确的。我没有各种各样的运行时和硬件环境来测试(我也没有时间)所以我不愿意给它 100% 的批准印章,但我认为我可以给你的实现竖起两个大拇指目前。

最后,尽管如此,我仍然会避免将其投入生产。是的,它可能是正确的,但是下一个必须维护代码的人可能不会理解它。有人可能会更改代码并破坏它,因为他们不了解更改的后果。您必须承认这段代码非常脆弱。即使是最轻微的改变也可能破坏它。

关于C#/CLR : MemoryBarrier and torn reads,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18935939/

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