Home > Blockchain >  What are the advantages of using `lock` over `SemaphoreSlim`?
What are the advantages of using `lock` over `SemaphoreSlim`?

Time:11-25

I'm late to the party, but I recently learned about SemaphoreSlim:

I used to use lock for synchronous locking, and a busy boolean for asynchronous locking. Now I just use SemaphoreSlim for everything.

private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

private void DoStuff()
{
    semaphoreSlim.Wait();
    try
    {
        DoBlockingStuff();
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

vs

private object locker = new object();

private void DoStuff()
{
    lock(locker)
    {
        DoBlockingStuff();
    }
}

Are there any synchronous cases where I should prefer using lock over SemaphoreSlim? If so, what are they?

CodePudding user response:

Here are a few advantages of the lock over the SemaphoreSlim:

  1. The lock is reentrant, while the SemaphoreSlim is not. So programming with the lock is more forgiving. In case there is a rare path in your app where you are acquiring the same lock twice, the lock will acquire it successfully, while the SemaphoreSlim will deadlock.

  2. The lock is syntactic sugar around the Monitor class. In other words there is language support for the Monitor in C#, and there isn't for the SemaphoreSlim. So using the lock is comparatively more convenient and less verbose.

  3. You can write more robust code with the lock, because you can add debugging assertions in auxiliary methods that the lock has been acquired: Debug.Assert(Monitor.IsEntered(_locker));

  4. You can get contention statistics with the Monitor.LockContentionCount property: "Gets the number of times there was contention when trying to take the monitor's lock." There are no statistics available for the SemaphoreSlim class.

  5. The SemaphoreSlim is IDisposable, so you have to think about when (and whether) to dispose it. Can you get away without disposing it? Are you disposing it prematurely and risk an ObjectDisposedException? These are questions that you don't have to answer with the lock.

  6. The lock can survive in the scenario of an aborted thread. It is translated by the C# compiler like this:

    bool lockTaken = false;
    try
    {
        Monitor.Enter(obj, ref lockTaken);
        DoBlockingStuff();
    }
    finally
    {
        if (lockTaken)
        {
            Monitor.Exit(obj);
        }
    }

The Monitor.Enter has been coded carefully so that in case the thread is aborted, the lockTaken will have the correct value. On the contrary the SemaphoreSlim.Wait is called outside of the try/finally block, so there is a small window that the current thread can be aborted without releasing the lock, resulting in a deadlock.

The .NET platform has dropped support for the Thread.Abort method, so you could rightfully say that the last point has only theoretical value.

  • Related