Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it atomic this "Int64" surrogate?

Tags:

c#

atomic

I am going to create a "long" (Int64) surrogate which has to be atomic, so its copy in a concurrent application will be inherently safe. I cannot use an Int32 because it spans too short as range.

I know that the atomicity should be guarantee as long the involved data can fit in a double-word (32 bits). No matter whether the OS is 32 or 64 bits.

Now, consider the following "long" surrogate...

NOTE: I've omitted several methods because I don't need them. In this case I need only the basic conversion to/from a real long.

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
}

Well, it seems working perfectly as I expect.

Here is a small test:

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();
    }
}

The SizeOf function yields always 4 bytes as the size of my surrogate. Does this value guarantees the atomicity of a copy SafeLong-to-SafeLong, or the 4 bytes should be interpreted as "real physical double-word"?

No matter the NON-atomicity of the long <--> SafeLong: it will be enclosed in a safe context.

Thanks a lot in advance.

like image 343
Mario Vernari Avatar asked Jul 13 '11 15:07

Mario Vernari


1 Answers

You're right in that this is atomic, but holy goodness is this ever a complicated solution to a simple problem. If you want a struct that has the value of a long but uses references to be atomic, just box the long! In fact, if you do that then you can make an atomic version of any struct type, so let's do that:

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); 
    }
}

And you're done. Why are you doing all this messing around with an array?

Also, I note that in your implementation you've got your explicit/implicit conversions backwards. A conversion should only be implicit if it is lossless and does not throw. Your implicit conversion from SafeLong to long can throw, so it should not be implicit. Your explicit conversion from long to SafeLong cannot throw and is lossless, so it could be implicit if you wanted it to be. As you can see, I've solved the problem in my implementation by making both directions lossless and non-throwing, so they are both implicit.

Notice that this struct is essentially a strongly-typed wrapper around a boxed value; if generic types had been available in the first version of the CLR, undoubtedly boxed value types would be implemented using some sort of type like this, just as nullable value types are similarly implemented by a special generic type.

The SizeOf function yields always 4 bytes as the size of my surrogate. Does this value guarantees the atomicity of a copy SafeLong-to-SafeLong?

Well, yes and no.

First off, the "SizeOf" method does not give the size of a struct in memory; it gives the size of the struct when it is persisted across a managed/unmanaged boundary. That's not necessarily the same as the size of a struct in managed memory; it often is the same, but it is not guaranteed to be the same. If you want to know the size of a struct in managed memory then you need to turn on "unsafe" mode and use the "sizeof" operator.

As a practical matter, a copy of a struct of size four is always going to be atomic, provided that it is copied to a location aligned on a four byte boundary. The language specification does not guarantee that any four-byte struct will be copied atomically, though in fact that is true in our implementation.

In your particular case, those four bytes are a reference to an array; the language specification does guarantee that a reference is always copied atomically.

like image 73
Eric Lippert Avatar answered Oct 17 '22 03:10

Eric Lippert