- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
只是在业余时间玩并发性,并想尝试在不使用读取器端锁定的情况下防止撕裂读取,以便并发读取器不会相互干扰。
这个想法是通过锁序列化写入,但在读取端仅使用内存屏障。这是一个可重用的抽象,它封装了我提出的方法:
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
}
}
}
最佳答案
我仔细查看了你的代码,它对我来说似乎是正确的。一件事立即让我感到震惊的是,您使用了一种既定的模式来执行低锁定操作。我可以看到您正在使用 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
}
}
++version
和
this.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/
VB.Net 没有等效的 C# volatile 关键字,因此您必须手动实现 volatile,这通常通过在读取之前和写入之后调用 Thread.MemoryBarrier() 来完成。所以这样的事情
using System; using System.Threading; using System.Threading.Tasks; class Program { static void
我修改了 Non-Blocking Synchronization 上给出的程序如下: class DemoProg { int _answer; bool _complete;
如果我理解正确,在 C# 中,一个 lock block 保证对一组指令的独占访问,但它也保证从内存中读取的任何内容都反射(reflect)了任何 CPU 缓存中该内存的最新版本.我们将 lock b
我需要一种可靠的方法来获得系统正常运行时间,并最终使用了如下方法。添加了一些评论以帮助人们阅读它。我不能使用任务,因为它必须在 .NET 3.5 应用程序上运行。 // This is a struc
由于似乎上下文切换可能发生在指令执行的任何时刻,所以我现在想知道为什么代码“部分有问题”(那两条指令)有意义,如果上下文切换可以在任何指令之间发生并且我们可能在不同的CPU上第二条指令的核心。 voi
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: Why we need Thread.MemoryBarrier()? 摘自 O'Reilly 的 C# i
在 Java 中,我如何显式触发一个完整的内存栅栏/屏障,等于调用 System.Threading.Thread.MemoryBarrier(); 在 C# 中? 我知道,自从 Java 5 读取和
对于以下场景,使用MemoryBarrier在线程安全性、结果和性能方面是否有任何差异 private SomeType field; public SomeType Property { g
在这本线程在线书籍中:http://www.albahari.com/threading/part4.aspx 有一个 Thread.MemoryBarrier() 的例子 class Foo
这个网站的新手,所以如果我没有以可接受的方式发帖请告诉我。 我经常按照下面的示例编写代码(为清楚起见,省略了 Dispose 之类的内容。)。我的问题是,是否需要如图所示的 volatile ?或者
在“C# 4 in a Nutshell”中,作者展示了这个类有时可以在没有 MemoryBarrier 的情况下写入 0,尽管我无法在我的 Core2Duo 中重现: public class Fo
在 .NET 中 lock关键字是 Monitor.Enter 周围的语法糖和 Monitor.Exit ,所以你可以说这段代码 lock(locker) { // Do something }
下面的 GLSL 计算着色器简单地复制了 inImage至 outImage .它源自更复杂的后处理过程。 在main()的前几行,单个线程将 64 个像素的数据加载到共享数组中。然后,在同步之后,6
根据Web,我找到了如下代码,相当于C# Volatile for VB.NET。 代码引用:How do I specify the equivalent of volatile in VB.net
只是在业余时间玩并发性,并想尝试在不使用读取器端锁定的情况下防止撕裂读取,以便并发读取器不会相互干扰。 这个想法是通过锁序列化写入,但在读取端仅使用内存屏障。这是一个可重用的抽象,它封装了我提出的方法
通过浏览 MEF 源代码,我找到了这篇文章。有人可以解释为什么锁内需要 MemoryBarrier 吗? 整个方法是: public void SatisfyImportsOnce(Composabl
假设我有一个字段控制某个循环的执行: private static bool shouldRun = true; 我有一个正在运行的线程,其代码如下: while(shouldRun) { /
在 msdn 上 http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208(v=vs.85).aspx , MemoryBarr
如果我正确理解 volatile 和 MemoryBarrier 的含义,那么下面的程序将永远无法显示任何结果。 每次我运行它时,它都会捕获写入操作的重新排序。我在 Debug 或 Release 中
我是一名优秀的程序员,十分优秀!