我将创建一个必须是原子的“long”(Int64)代理,因此它在并发应用程序中的副本本质上是安全的。我不能使用Int32,因为它跨越范围太短。
我知道,只要涉及的数据可以适合双字(32位),原子性就应该得到保证。无论操作系统是32位还是64位。
现在,考虑以下“长”代理......
注意:我省略了几种方法,因为我不需要它们。在这种情况下,我只需要基本转换为/来自真正的长。
public struct SafeLong
: IConvertible
{
public SafeLong(long value)
{
unchecked
{
var arg = (ulong)value;
this._data = new byte[]
{
(byte)arg,
(byte)(arg >> 8),
(byte)(arg >> 16),
(byte)(arg >> 24),
(byte)(arg >> 32),
(byte)(arg >> 40),
(byte)(arg >> 48),
(byte)(arg >> 56),
};
}
}
private byte[] _data;
private long Value
{
get
{
unchecked
{
var lo =
this._data[0] |
this._data[1] << 8 |
this._data[2] << 16 |
this._data[3] << 24;
var hi =
this._data[4] |
this._data[5] << 8 |
this._data[6] << 16 |
this._data[7] << 24;
return (long)((uint)lo | (ulong)(uint)hi << 32);
}
}
}
public static implicit operator long(SafeLong value)
{
return value.Value; // implicit conversion
}
public static explicit operator SafeLong(long value)
{
return new SafeLong(value); // explicit conversion
}
#region IConvertible implementation
public TypeCode GetTypeCode()
{
return Type.GetTypeCode(typeof(SafeLong));
}
public object ToType(Type conversionType, IFormatProvider provider)
{
return Convert.ChangeType(this.Value, conversionType);
}
public long ToInt64(IFormatProvider provider)
{
return this.Value;
}
// ... OMISSIS (not implemented) ...
#endregion
}
好吧,它看起来像我期望的那样完美。
这是一个小测试:
class Program
{
static void Main(string[] args)
{
var sla = (SafeLong)12345678987654321L;
var la = (long)sla;
Console.WriteLine(la);
var slb = (SafeLong)(-998877665544332211L);
var lb = (long)slb;
Console.WriteLine(lb);
Console.WriteLine(Marshal.SizeOf(typeof(SafeLong)));
Console.WriteLine(Marshal.SizeOf(sla));
Console.WriteLine(Marshal.SizeOf(slb));
long lc = new SafeLong(556677);
var slc = slb;
Console.WriteLine(slc);
slc = (SafeLong)lc;
Console.WriteLine(slc);
Console.WriteLine(slb);
Console.Write("Press any key...");
Console.ReadKey();
}
}
SizeOf函数总是产生4个字节作为我的代理的大小。这个值是否保证了SafeLong-to-SafeLong副本的原子性,或者4个字节应该被解释为“真正的物理双字”?
无论长&lt; - &gt;的非原子性如何。 SafeLong:它将被包含在安全的环境中。
提前多多感谢。
答案 0 :(得分:13)
你是对的,这是原子的,但圣洁的善良对于一个简单的问题来说这是一个复杂的解决方案。如果你想要一个具有long值但是使用引用是原子的结构,那就把它打包!事实上,如果你这样做,那么你可以制作任何结构类型的原子版本,所以让我们这样做:
public struct SafeThing<T> where T : struct
{
private object boxedThing;
public SafeThing(T value)
{
boxedThing = value;
}
public T Value { get { return boxedThing == null ? default(T) : (T)boxedThing; } }
public static implicit operator T(SafeThing<T> value)
{
return value.Value;
}
public static implicit operator SafeThing<T>(T value)
{
return new SafeThing(value);
}
}
你已经完成了。你为什么要用数组搞乱所有这些呢?
另外,我注意到在您的实现中,您已经向后转换了显式/隐式转换。如果转换无损且不抛出,则只应隐式转换。您从SafeLong到long的隐式转换可以抛出,因此它不应该是隐式的。你从long到SafeLong的显式转换不能抛出并且是无损的,所以如果你想要的话它可能是隐含的。正如你所看到的,我已经通过使两个方向都无损且无投掷来解决我的实现中的问题,因此它们都是隐含的。
请注意,此结构本质上是一个围绕盒装值的强类型包装器;如果在CLR的第一个版本中可以使用泛型类型,毫无疑问,盒装值类型将使用某种类型的类似实现,就像可空值类型类似地通过特殊泛型类型实现一样。
SizeOf函数总是产生4个字节作为我的代理的大小。这个值是否保证了SafeLong-to-SafeLong副本的原子性?
嗯,是的,不是。
首先,“SizeOf”方法不会在内存中给出结构的大小;它提供了结构在托管/非托管边界上持久化时的大小。这不一定与托管内存中结构的大小相同; 经常是相同的,但保证是相同的。如果您想知道托管内存中结构的大小,则需要打开“不安全”模式并使用“sizeof”运算符。
实际上,只要复制到四字节边界上对齐的位置,大小为四的结构的副本总是原子的。 语言规范不保证任何四字节结构将以原子方式复制,但实际上在我们的实现中也是如此。
在您的特定情况下,这四个字节是对数组的引用;语言规范 保证始终以原子方式复制引用。