Synchronization Primitives
Synchronization primitives are mechanisms that coordinate concurrent execution between multiple threads to avoid race conditions and ensure data consistency.
Examples:
- Mutex
- Spinlock
- Semaphore
- Condition Variable
- Futex
- Memory Barrier/Fence
Mutex
In multithreading, a mutex (mutual exclusion) is a synchronization primitive that prevents multiple threads from simultaneously accessing shared resources.
It acts as a lock that only one thread can hold at a time - when a thread acquires the mutex, others attempting to access the same resource must wait until the mutex is released
The use of mutexes can prevent race conditions, but may also introduce performance bottlenecks and deadlocks.
Basic Mutex API:
int mutex_init(mutex_t *mutex); int mutex_lock(mutex_t *mutex); // blocking, waits until mutex available int mutex_trylock(mutex_t *mutex); // non-blocking, returns with success or failure int mutex_unlock(mutex_t *mutex); // unlock a mutex int mutex_destroy(mutex_t *mutex);
Spinlock
A spinlock is a synchronization primitive used in multithreading where a thread repeatedly checks a condition until it becomes available ("spinning").
Spinlocks cause CPU load (unlike mutexes) and block execution by actively spinning. However, they avoid the overhead of context switching, which makes them suitable for for brief waits or very short critical sections.
Code Example
typedef volatile int spinlock_t; void spin_lock(spinlock_t *lock) { while (__sync_lock_test_and_set(lock, 1)) { // Spin until lock is acquired } } void spin_unlock(spinlock_t *lock) { __sync_lock_release(lock); }
Semaphore
Semaphores are synchronization primitives used to control access to shared resources in concurrent programming. They maintain a counter that represents the number of available resources.
Main types:
- Binary semaphores: Counter values are 0 or 1 (functioning like mutexes)
- Counting semaphores: Counter can have arbitrary non-negative values
Core operations:
wait/P/down: Decrement counter if it's > 0, otherwise blocks until it cansignal/V/up: Increment counter, potentially unblocking waiting processes
Basic Semaphore API:
typedef struct { int value; /* ... */ } semaphore; void sem_init(semaphore *sem, int value); void sem_wait(semaphore *sem); int sem_trywait(semaphore *sem); // non-blocking void sem_post(semaphore *sem); int sem_value(semaphore *sem, int *sval); void sem_destroy(semaphore *sem);
Condition Variable
Condition variables are synchronization primitives used in multithreading that allow threads to wait until a condition is met. During this time, threads are suspended (sleeping), which avoids busy waiting (spinning).
They are typically used alongside mutexes to solve the producer-consumer problem and similar synchronization scenarios.
Usage:
- Thread atomically releases a mutex and enters wait state
- Waiting thread awakens by signal (one thread) or broadcast (all threads)
- Upon waking, thread reacquires mutex before proceeding
Basic Condition Variable API
int cond_init(cond_t **cond); int cond_wait(cond_t *cond, mutex_t *mutex); int cond_timedwait(cond_t *cond, mutex_t *mutex, uint64_t timeout_ms); int cond_signal(cond_t *cond); // awaken single waiting thread int cond_broadcast(cond_t *cond); // awaken all waiting threads int cond_destroy(cond_t *cond);