C# 在有保证的64位体系结构上实现long属性的线程安全原子访问器的最佳方法
我有一个属性,它的类型是long(Int64) 如果是int,那么我可以将支持字段声明为:C# 在有保证的64位体系结构上实现long属性的线程安全原子访问器的最佳方法,c#,.net,multithreading,C#,.net,Multithreading,我有一个属性,它的类型是long(Int64) 如果是int,那么我可以将支持字段声明为: private volatile int _myInt; 并创建一个简单的get和set访问器 但是,C#编译器不允许将volatile关键字与long类型一起使用,即使使用x64项目设置也是如此。因此,情况是,即使我们确信读/写操作在此变量上是原子的,但不幸的是,读取该变量的线程将获得并使用旧的(处理器或CLR/JIT优化器)缓存值 问题1:这是否意味着我必须使用Interlocked来防止读取缓存的
private volatile int _myInt;
并创建一个简单的get和set访问器
但是,C#编译器不允许将volatile关键字与long类型一起使用,即使使用x64项目设置也是如此。因此,情况是,即使我们确信读/写操作在此变量上是原子的,但不幸的是,读取该变量的线程将获得并使用旧的(处理器或CLR/JIT优化器)缓存值
问题1:这是否意味着我必须使用Interlocked来防止读取缓存的值,而不是简单地在get访问器中读取该值
get
{
return Interlocked.CompareExchange(ref _myLong, 0, 0);
}
这意味着相当高的开销
问题2:仍然假设一个有保证的64位体系结构,在set访问器中简单的赋值就足够了吗
set
{
_myLong = value;
}
提前感谢您可以使用来解决此问题。例如:
class Example {
private long _prop;
public long prop {
get { return Volatile.Read(ref _prop); }
set { Volatile.Write(ref _prop, value); }
}
}
static void Main(string[] args) {
var obj = new Example();
obj.prop = 42;
Console.WriteLine(obj.prop);
}
虽然这看起来效率低下,但实际上它在x64处理器上生成了高效的代码。jitter具有Volatile类的内置知识,并直接将其转换为机器代码,而不依赖于框架实现。x64抖动知道long在64位Intel/AMD处理器上是原子的。例如:
class Example {
private long _prop;
public long prop {
get { return Volatile.Read(ref _prop); }
set { Volatile.Write(ref _prop, value); }
}
}
static void Main(string[] args) {
var obj = new Example();
obj.prop = 42;
Console.WriteLine(obj.prop);
}
生成此机器代码:
00007FFA3DF43AB0 sub rsp,28h ; setup stack frame
00007FFA3DF43AB4 lea rcx,[7FFA3DF959B0h] ; obj = new Example
00007FFA3DF43ABB call 00007FFA9D5A2300
00007FFA3DF43AC0 mov qword ptr [rax+8],2Ah ; obj.prop setter
00007FFA3DF43AC8 mov rcx,qword ptr [rax+8] ; obj.prop getter
00007FFA3DF43ACC call 00007FFA9CD0CFD0 ; Console.WriteLine
00007FFA3DF43AD1 nop ; alignment
00007FFA3DF43AD2 add rsp,28h ; destroy stack frame
00007FFA3DF43AD6 ret ; done
并注意如何完全消除属性getter和setter,直接访问示例。这就是你要找的。如果您曾经在具有弱内存模型的处理器(如ARM)上运行此代码,那么它仍然可以正常工作,并根据此类处理器的要求生成相应的获取和释放语义。您可以使用来解决此问题。例如:
class Example {
private long _prop;
public long prop {
get { return Volatile.Read(ref _prop); }
set { Volatile.Write(ref _prop, value); }
}
}
static void Main(string[] args) {
var obj = new Example();
obj.prop = 42;
Console.WriteLine(obj.prop);
}
虽然这看起来效率低下,但实际上它在x64处理器上生成了高效的代码。jitter具有Volatile类的内置知识,并直接将其转换为机器代码,而不依赖于框架实现。x64抖动知道long在64位Intel/AMD处理器上是原子的。例如:
class Example {
private long _prop;
public long prop {
get { return Volatile.Read(ref _prop); }
set { Volatile.Write(ref _prop, value); }
}
}
static void Main(string[] args) {
var obj = new Example();
obj.prop = 42;
Console.WriteLine(obj.prop);
}
生成此机器代码:
00007FFA3DF43AB0 sub rsp,28h ; setup stack frame
00007FFA3DF43AB4 lea rcx,[7FFA3DF959B0h] ; obj = new Example
00007FFA3DF43ABB call 00007FFA9D5A2300
00007FFA3DF43AC0 mov qword ptr [rax+8],2Ah ; obj.prop setter
00007FFA3DF43AC8 mov rcx,qword ptr [rax+8] ; obj.prop getter
00007FFA3DF43ACC call 00007FFA9CD0CFD0 ; Console.WriteLine
00007FFA3DF43AD1 nop ; alignment
00007FFA3DF43AD2 add rsp,28h ; destroy stack frame
00007FFA3DF43AD6 ret ; done
并注意如何完全消除属性getter和setter,直接访问示例。这就是你要找的。如果您曾经在内存模型较弱的处理器(如ARM)上运行过此代码,那么它仍然可以正常工作,并按照这样的处理器的要求生成相应的获取和释放语义。许多著名的C#luminaries过去都曾谈到过此类问题,我不敢把自己的任何话告诉他们(因此,很抱歉,没有提供摘要。)请访问这些luminaries博客文章并欣赏,等等。我会让其他人添加到其他博客文章的链接。@rwong:我已经仔细阅读了您提供的链接。虽然它很清楚,并且根据我的判断都是正确的,但它并没有说明我所暴露的问题。另外,我的问题是一个关于性能的实施问题d正确性,而非理论性。许多著名的C#名人过去都曾就此类问题发表过意见,我不敢向他们说任何我自己的话(因此,很抱歉,没有提供总结)请访问这些luminaries博客文章并欣赏,等等。我会让其他人添加到其他博客文章的链接。@rwong:我已经仔细阅读了您提供的链接。虽然它很清楚,并且根据我的判断都是正确的,但它并没有说明我所暴露的问题。另外,我的问题是一个关于性能的实施问题d正确性和非理论性。详细信息请参阅Thx。这肯定是我所缺少的知识,您能解释一下如何读取内存位置(mov rcx、qword ptr…)确保读取最新的值,我的意思是它是如何使处理器数据缓存无效的?嗯,这不是你要求的。简单地使用获得该保证的lock语句。看到这个机器代码,我真正不理解的是,围栏在哪里?最终的代码不应该产生与volatile关键字doe相同的语义吗s?(我是指Volatile.Read()和Volatile.Write())确实如此。请阅读底部的部分,“如果您曾经在内存模型较弱的处理器上运行此代码”.x64并不弱。当您显式使用volatile.Thx时,请尝试查看机器代码以了解详细信息。这肯定是我所缺少的知识,您能否解释如何读取内存位置(mov rcx、qword ptr…)确保读取最新的值,我的意思是它是如何使处理器数据缓存无效的?嗯,这不是你要求的。简单地使用获得该保证的lock语句。看到这个机器代码,我真正不理解的是,围栏在哪里?最终的代码不应该产生与volatile关键字doe相同的语义吗s?(我是指Volatile.Read()和Volatile.Write())确实如此。请阅读底部的部分,“如果您曾经在具有弱内存模型的处理器上运行此代码”。x64并不弱。显式使用Volatile时,请尝试查看机器代码。