Synchronization primitives and concurrent collections

1 min read

When multiple threads access shared resources, synchronization is necessary to prevent data corruption and to ensure orderly execution. .NET provides several synchronization primitives:

  • lock statement (syntactic sugar for System.Threading.Monitor) gives exclusive access to a block of code for a given object, so only one thread can execute it at a time.
  • SemaphoreSlim limits the number of threads that can access a resource, or a pool of resources, concurrently.
  • Mutex is similar to lock, but can be used for inter-process synchronization.
  • Monitor.Wait(), Monitor.Pulse() and Monitor.PulseAll() are used for more complex coordination patterns, where threads need to wait for specific conditions to be met.
  • ReaderWriterLockSlim allows multiple threads to read a resource concurrently, but gives exclusive access to a single thread for writing. It’s useful for read-heavy, write-infrequent scenarios.

Concurrent collections

The collections in System.Collections.Concurrent, for example ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T> and ConcurrentBag<T>, are designed to be thread-safe for common operations. That reduces the need for manual locking.

Without them, shared data is open to race conditionsRace conditionsThe result depends on the order in which threads happen to run.Opens in a new tab, and locks taken in the wrong order cause deadlocksDeadlocksTwo or more threads wait on each other forever.Opens in a new tab.