English
Summary
Conclusions
Shared data accessed by multiple threads creates race conditions: even counter++ consists of several actions and can lose updates. Correct synchronization ensures safety, liveness, and fairness. The cheapest approach is to eliminate shared state; use atomic Interlocked operations and CAS loops for individual variables, and lock with a dedicated Lock object or Monitor for compound actions. The Monitor.Wait/PulseAll condition variables let threads wait for a condition inside a critical section; SemaphoreSlim limits the number of threads, ReaderWriterLockSlim allows multiple readers, and ManualResetEventSlim, CountdownEvent, and Barrier coordinate threads. Named Mutex and Semaphore instances synchronize processes. Deadlock occurs under the four Coffman conditions; in practice, lock ordering and timeouts prevent it, while Rider (Parallel Stacks) and dotnet-dump help diagnose it. Besides deadlock, starvation, livelock, priority inversion, and lock convoys compromise liveness. Every critical section executes sequentially, so the choice of synchronization tool and lock granularity must be checked through measurement.
Self-check questions
- What are shared state, a critical section, and a race condition?
- Why is
counter++not atomic? What other operations are non-atomic? - State the requirements for safety, liveness, and fairness.
- State Bernstein's conditions and give an example of their violation.
- What methods does the
Interlockedclass provide? How does a CAS loop work? - What is the
volatilemodifier for, and what does it not guarantee? - What does the compiler translate a
lockstatement into for theLocktype and for other types? - Which objects must not be used for locking, and why?
- How do
Monitor.Wait,Pulse, andPulseAllwork? Why is the condition checked in awhileloop? - How do
Mutex,Semaphore, andSemaphoreSlimdiffer? - When is
ReaderWriterLockSlimmore efficient thanlock? What is upgradeable mode? - How do
ManualResetEventSlim,AutoResetEvent,CountdownEvent, andBarrierdiffer? - Name the Coffman conditions. Which deadlock prevention techniques break each one?
- How can you find a deadlock using Rider and
dotnet-dump? - What are starvation, livelock, priority inversion, and a lock convoy?
Useful links
- Overview of synchronization primitives: https://learn.microsoft.com/dotnet/standard/threading/overview-of-synchronization-primitives
- Managed threading best practices: https://learn.microsoft.com/dotnet/standard/threading/managed-threading-best-practices
- The
lockstatement: https://learn.microsoft.com/dotnet/csharp/language-reference/statements/lock - The
Lockclass: https://learn.microsoft.com/dotnet/api/system.threading.lock - The
Interlockedclass: https://learn.microsoft.com/dotnet/api/system.threading.interlocked SemaphoreandSemaphoreSlim: https://learn.microsoft.com/dotnet/standard/threading/semaphore-and-semaphoreslim- The
dotnet-dumputility: https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dump - Debugging deadlock: https://learn.microsoft.com/dotnet/core/diagnostics/debug-deadlock
- Multithreaded debugging in Rider: https://www.jetbrains.com/help/rider/Debugging_Multithreaded_Applications.html