Testing Locks is Difficult
03/16/2025
I have recently worked with multithreaded code using shared state, that required mutual exclusion. In .NET there are numerous different types to accomplish mutual exclusion, the C# language even provides a lock
keyword. Depending on the use-case one can use Monitor
, lock
on reference types, SemaphoneSlim
, ReaderWriterLockSlim
, Interlocked
, SpinLock
concurrent collections, or the .NET 9 introduced Lock
type. This latter one also works with the lock
language keyword, and it brings much of the underlying native implementation to the managed space. This results in some minor performance improvements compared to locking on any other reference type.
Achieving the above improvement seems straightforward: change the object's type that is used within a lock
keyword. However, lock
might be still slower to other types of locks as the 'best' locking solution depends on many factors:
number of threads accessing the lock
the frequence of the threads accessing the lock
duration inside the lock
contention
CPU, architecture, OS, etc.
memory model
etc.
In general, certain locks might fit better for certain uses cases (for example, when using collection, async code, granular locks etc.), which drives the decisions on the lock that suits the best for an application. To prove that one locking solution is more performant than another is only possible by performance measuring an actual application on a production-like load. This also means that the same code might perform differently from application to application.