6.7 KiB
Synchronization Primitives
Non-waitable synchronization objects for protecting shared resources in multi-threaded environments.
Lock Selection Guidelines
Only use SpinLock when the protected code exists super fast.
Only use CriticalSection when the protected code costs time.
SpinLock
Use SpinLock for protecting very fast code sections.
SpinLock Operations
Enterblocks the current thread, and when it returns, the current thread owns the spin lock.- Only one thread owns the spin lock.
TryEnterdoes not block the current thread, and there is a chance that the current thread will own the spin lock, indicated by the return value.
Leavereleases the spin lock from the current thread.
SpinLock Automation
Use SPIN_LOCK, CS_LOCK, READER_LOCK, WRITER_LOCK macros for exception-safe automatic locking.
When it is able to Enter and Leave in the same function, use SPIN_LOCK to simplify the code.
It is also exception safety, so you don't need to worry about try-catch:
SpinLock lock;
SPIN_LOCK(lock)
{
// fast code that owns the spin lock
}
CriticalSection
Use CriticalSection for protecting time-consuming code sections.
CriticalSection Operations
Enterblocks the current thread, and when it returns, the current thread owns the critical section.- Only one thread owns the critical section.
TryEnterdoes not block the current thread, and there is a chance that the current thread will own the critical section, indicated by the return value.
Leavereleases the critical section from the current thread.
CriticalSection Automation
When it is able to Enter and Leave in the same function, use CS_LOCK to simplify the code.
It is also exception safety, so you don't need to worry about try-catch:
CriticalSection cs;
CS_LOCK(cs)
{
// slow code that owns the critical section
}
ReaderWriterLock
Use ReaderWriterLock for multiple reader, single writer scenarios.
ReaderWriterLock is an advanced version of CriticalSection:
- Multiple threads could own the same reader lock. When it happens, it prevents any thread from owning the writer lock.
- Only one threads could own the writer lock. When it happens, it prevents any thread from owning the reader lock.
ReaderWriterLock Operations
- Call
TryEnterReader,EnterReaderandLeaveReaderto access the reader lock. - Call
TryEnterWriter,EnterWriterandLeaveWriterto access the writer lock.
ReaderWriterLock Automation
When it is able to EnterReader and LeaveReader in the same function, use READER_LOCK to simplify the code.
When it is able to EnterWriter and LeaveWriter in the same function, use WRITER_LOCK to simplify the code.
They are also exception safety, so you don't need to worry about try-catch:
ReaderWriterLock rwlock;
READER_LOCK(rwlock)
{
// code that owns the reader lock
}
WRITER_LOCK(rwlock)
{
// code that owns the writer lock
}
ConditionVariable
Use ConditionVariable with SleepWith, SleepWithForTime for conditional waiting.
A ConditionVariable works with a CriticalSection or a ReaderWriterLock.
ConditionVariable with CriticalSection
- Call
SleepWithto work with aCriticalSection. It works on both Windows and Linux. - Call
SleepWithForTimeto work with aCriticalSectionwith a timeout. It only works on Windows.
ConditionVariable with ReaderWriterLock
- Call
SleepWithReader,SleepWithReaderForTime,SleepWriterorSleepWriterForTimeto work with aReaderWriterLock. They only work on Windows.
ConditionVariable Behavior
The Sleep* function temporarily releases the lock from the current thread, and block the current thread until it owns the lock again.
- Before calling the
Sleep*function, the current thread must own the lock. - Calling the
Sleep*function releases the lock from the current thread, and block the current thread. - The
Sleep*function returns whenWakeOnePendingorWaitAllPendingsis called.- The
Sleep*ForTimefunction could also return when it reaches the timeout. But this will not always happen, because:WaitOnePendingonly activates one thread pending on the condition variable.WaitAllPendingsactivates all thread but they are also controlled by the lock.
- When
Sleep*returns, the current thread owns the lock.
- The
ConditionVariable Signaling
Use WakeOnePending, WaitAllPendings for condition variable signaling.
Extra Content
Lock Performance Characteristics
Different synchronization primitives have varying performance profiles:
- SpinLock: Lowest overhead for very short critical sections, but wastes CPU cycles if held too long
- CriticalSection: Higher overhead but efficient for longer critical sections
- ReaderWriterLock: Most complex but allows concurrent reads
Choosing the Right Primitive
Selection criteria for synchronization primitives:
- SpinLock: Use when critical section execution time is less than a context switch (~50-100 microseconds)
- CriticalSection: Use for general-purpose mutual exclusion with longer critical sections
- ReaderWriterLock: Use when reads are frequent and writes are infrequent
- ConditionVariable: Use when threads need to wait for specific conditions
Exception Safety
All automation macros (SPIN_LOCK, CS_LOCK, READER_LOCK, WRITER_LOCK) provide RAII-style exception safety:
- Locks are automatically released when leaving the scope
- Proper cleanup even if exceptions are thrown
- No manual lock management required
Platform Differences
Synchronization behavior varies across platforms:
- Windows: Full support for all features including timeout operations
- Linux: Limited timeout support for some operations
- Cross-platform code should avoid Windows-only features when possible
Deadlock Prevention
Best practices to avoid deadlocks:
- Always acquire locks in the same order across all threads
- Use timeout versions of lock operations when available
- Keep critical sections as short as possible
- Consider using higher-level synchronization abstractions
Performance Optimization
Tips for optimal synchronization performance:
- Minimize lock contention by keeping critical sections short
- Use appropriate lock granularity (not too fine, not too coarse)
- Consider lock-free alternatives for high-performance scenarios
- Profile lock contention in performance-critical applications
Integration with Threading
Synchronization primitives work best when combined with the multi-threading APIs:
- Use with
ThreadPoolLitefor concurrent task execution - Combine with
ConditionVariablefor complex coordination scenarios - Apply appropriate synchronization for shared data structures