Search code examples
c#multithreadingthread-safety

A thread-safe Integer class in C#


After my previous question, I tried to fix the code but still the output is not what I expect. I tried to defined a thread safe integer:

using System;
using System.Threading;

public struct SInt
{
    private int _value;

    public SInt(int initialValue = 0)
    {
        _value = initialValue;
    }

    public int Value => Volatile.Read(ref _value); // Interlocked.CompareExchange(ref _value, 0, 0);

    // Add a value
    public int Add(int value) => Interlocked.Add(ref _value, value);

    // Subtract a value
    public int Subtract(int value) => Interlocked.Add(ref _value, -value);

    // Multiply the value
    public int Multiply(int value)
    {
        int initial, computed;
        do
        {
            initial = Value;
            computed = initial * value;
        }
        while (Interlocked.CompareExchange(ref _value, computed, initial) != initial);

        return computed;
    }

    // Divide the value
    public int Divide(int value)
    {
        if (value == 0)
            throw new DivideByZeroException();

        int initial, computed;
        do
        {
            initial = Value;
            computed = initial / value;
        }
        while (Interlocked.CompareExchange(ref _value, computed, initial) != initial);
        return computed;
    }

    // Increment the value
    public int Increment() => Interlocked.Increment(ref _value);

    // Decrement the value
    public int Decrement() => Interlocked.Decrement(ref _value);

    // Overloaded operators
    public static SInt operator +(SInt a, int b)
    {
        a.Add(b);
        return a;
    }

    public static SInt operator -(SInt a, int b)
    {
        a.Subtract(b);
        return a;
    }

    public static SInt operator *(SInt a, int b)
    {
        a.Multiply(b);
        return a;
    }

    public static SInt operator /(SInt a, int b)
    {
        a.Divide(b);
        return a;
    }

    public static SInt operator ++(SInt a)
    {
        a.Increment();
        return a;
    }

    public static SInt operator --(SInt a)
    {
        a.Decrement();
        return a;
    }

    // Equality operators
    public static bool operator ==(SInt a, SInt b) => a.Value == b.Value;

    public static bool operator !=(SInt a, SInt b) => a.Value != b.Value;

    // Comparison operators
    public static bool operator <(SInt a, SInt b) => a.Value < b.Value;
    public static bool operator <=(SInt a, SInt b) => a.Value <= b.Value;
    public static bool operator >(SInt a, SInt b) => a.Value > b.Value;
    public static bool operator >=(SInt a, SInt b) => a.Value >= b.Value;

    // Implicit conversion from int to SInt
    public static implicit operator SInt(int value) => new SInt(value);

    // Implicit conversion from SInt to int
    public static implicit operator int(SInt sInt) => sInt.Value;

    public override bool Equals(object? obj)
    {
        if (obj is SInt other)
        {
            return this == other;
        }
        return false;
    }

    public override int GetHashCode() => Value.GetHashCode();

    public override string ToString() => Value.ToString();
}

Then I rewrote the program:

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        List<Thread> threads = new List<Thread>();
        SInt s = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 0; i <1000; i++)
        {
            var t = new Thread(() =>
            {
                s++;
                Thread.Sleep(1000);
            });
            t.Priority = ThreadPriority.Highest;
            threads.Add(t);
            t.Start();
        }
        foreach (var t in threads)
            t.Join();
        stopwatch.Stop();
        Console.WriteLine($"Time: {stopwatch.ElapsedMilliseconds / 1000.0} Seconds");
        Console.WriteLine(s);
        Console.WriteLine(threads.Count(t => t.ThreadState == System.Threading.ThreadState.Stopped));
        Console.ReadKey();
    }
}

output in my laptop:

Time: 85.16 Seconds
989
1000

I expect that 989 be 1000.

What's wrong?


Solution

  • In C# a struct is a value type.

    Therefore when you return it e.g. in:

    public static SInt operator ++(SInt a)
    {
        a.Increment();
        return a;   // <-- here a copy is returned
    }
    

    You actually return a copy.

    If SInt was a class it would behave like you expect, because being a reference type it would return a reference to the current instance.

    Note:
    As @user555045 commented above even if you solve this specfic problem of using operator++, you will still have problems when it is used in more complex expressions.