它是原子的吗?代理

本文关键字:代理 原子的 | 更新日期: 2023-09-27 18:02:35

我将创建一个"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字节作为代理的大小。这个值是否保证了saflong -to- safelong副本的原子性,或者这4个字节应该被解释为"真正的物理双字"?

无论长<--> SafeLong:是否具有非原子性,它都将被包含在一个安全的上下文中。

它是原子的吗?代理

你是对的,这是原子的,但是上帝啊,这是一个简单问题的复杂解决方案。如果您想要一个具有long值但使用原子引用的结构体,只需将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字节作为代理的大小。这个值是否保证副本从saflong到safelong的原子性?

嗯,是也不是。

首先,SizeOf方法没有给出结构体在内存中的大小;它给出了结构体跨托管/非托管边界持久化时的大小。这与托管内存中结构体的大小不一定相同;它通常是相同的,但是不能保证是相同的。如果你想知道托管内存中结构体的大小,那么你需要打开"不安全"模式并使用"sizeof"操作符。

作为一个实际问题,一个大小为4的结构体的副本总是原子的,只要它被复制到一个在4字节边界上对齐的位置。语言规范不保证任何四字节结构都将被自动复制,尽管实际上在我们的实现中是如此。

在你的特殊情况下,这四个字节是对数组的引用;语言规范保证引用总是被自动复制。