Search code examples
c#lockingsingletonparallel.foreach

Force Serial Singleton Function Lock


I'm wanting to run a parallel process using a thread-safe singleton for a portion of that process. I'm using COM Interop in the Singleton and can only have one instance running at a time system-wide.

The Singleton's operations cannot overlap. When a Singleton operation is called, there can't be any other singleton operations currently in-process..

I have a simplified, working example that using lock to ensure the singleton operation stay serialized across the multiple threads.

Would anyone recommend a more optimal solution than what I have currently OR is what I have implemented appropriate? Thank you.

Current working example-

internal class Program
{
    static void Main(string[] args)
    {
        Parallel.ForEach(Enumerable.Range(0, 10), i =>
        {
            if (i % 2 == 0)
                SerializedSingleton.Instance.Serializer(); // Serialized Operation Only
            else 
                Thread.Sleep(100); // Concurrent Operations
        });
    }
}

public sealed class SerializedSingleton
{
    private static readonly object SerializerLock = new object();
    // THREAD-SAFE SINGLETON SETUP
    private static readonly Lazy<SerializedSingleton> lazy =
    new Lazy<SerializedSingleton>(() => new SerializedSingleton());
    public static SerializedSingleton Instance { get => lazy.Value; }
    private SerializedSingleton()
    {
    }
    public void Serializer()
    {
        lock (SerializerLock) 
        {
            Console.WriteLine("Serializer START");
            Thread.Sleep(2000);
            Console.WriteLine("Serializer STOP");
        }
    }
}

Current Output (desired)-

Serializer START
Serializer STOP
Serializer START
Serializer STOP
Serializer START
Serializer STOP
Serializer START
Serializer STOP
Serializer START
Serializer STOP

Need to avoid-

Serializer START
Serializer START
Serializer START
Serializer START
Serializer START
Serializer STOP
Serializer STOP
Serializer STOP
Serializer STOP
Serializer STOP

Thead-safe singleton sourced from: https://csharpindepth.com/Articles/Singleton


Solution

  • Whenever you call lock (SerializerLock) and Thread.Sleep(..), you block the current thread. If there is high contention, you will have a lot of threads waiting for the lock. That has a little impact on your CPU, and reduces the number of threads available in the thread pool. An alternative would be to use SemaphoreSlim, and make the locking asynchronous:

    public sealed class SerializedSingleton
    {
        // This doesn't need to be static if the instance is a singleton
        private readonly SemaphoreSlim serializerLock = new SemaphoreSlim(1, 1);
    
        private static readonly Lazy<SerializedSingleton> lazy =
            new Lazy<SerializedSingleton>(() => new SerializedSingleton());
    
        public static SerializedSingleton Instance { get => lazy.Value; }
    
        public async Task SerializerAsync()
        {
            await serializerLock.WaitAsync();
    
            try
            {
                Console.WriteLine("Serializer START");
                await Task.Delay(2000);
                Console.WriteLine("Serializer STOP");
            }
            finally
            {
                serializerLock.Release();
            }
        }
    }