Why counter++ is not atomic
counter++ looks like one operation in C. At the machine level it's three:
load the value from memory into a register, increment the register, store it back.
If two threads execute this sequence and the scheduler interleaves them,
both threads can read the same value, both increment it to the same result,
and both store the same value — so two increments produce a net increment of one.
-fsanitize=thread to catch races at runtime.
Mutexes
A mutex (mutual exclusion lock) ensures that only one thread executes the
protected region at a time. pthread_mutex_lock acquires the lock —
blocking if another thread holds it. pthread_mutex_unlock releases it.
The region between lock and unlock is the critical section.
Deadlock
Deadlock occurs when two (or more) threads are each waiting for a lock held by the other, so neither can proceed. It happens when locks are acquired in inconsistent orders.
The fix: always acquire locks in the same global order. If every thread that needs both A and B always locks A before B, deadlock is impossible.
Condition variables
A mutex alone only prevents concurrent access. Sometimes a thread needs to wait for a condition — not just a lock. A condition variable lets a thread sleep (releasing the mutex atomically) until another thread signals that something changed.
Any unsynchronized read-modify-write on shared data is a race — use a mutex to make the critical section atomic, and always acquire multiple locks in a consistent order to prevent deadlock.