ECE344 Fall 2026 (Sec 1) Lec 18 - Semaphores
Watch on YouTube →
Overview
Jon Eyolfson distinguishes mutexes, which provide mutual exclusion and prevent data races, from semaphores, which coordinate event order through atomic waits and posts. He builds ordered-print examples and a bounded-buffer producer-consumer solution using counting semaphores for filled and empty slots, while retaining mutexes to protect shared indices.
Key takeaways
- A mutex prevents simultaneous access to a critical section, but it does not establish execution order between threads.
- A semaphore initialized to zero acts as a one-way signal: sem_wait blocks until another thread calls sem_post.
- For ordered printing, use a separate semaphore for each required ordering relationship, such as first-to-second and second-to-third.
- A bounded buffer of size four needs a filled-slots semaphore initialized to zero and an empty-slots semaphore initialized to four.
- Semaphores do not replace mutexes for protecting shared state: producer and consumer indices still need mutex protection.
- Initializing both producer-consumer semaphores to zero creates deadlock because every thread blocks before any can post.
Chapters
0:00
Locks, Data Races, and Thread Ordering
- A data race occurs when concurrent accesses target the same memory location and at least one access is a write.
- A mutex protects a critical section by allowing only one thread to execute it at a time.
- Mutexes provide mutual exclusion, but do not specify which thread runs first.
8:30
Why Two Created Threads Do Not Print in Order
- The example creates separate threads for print-first and print-second functions, then joins both before main exits.
- Thread creation order does not guarantee execution order, so either message may appear first.
- The desired constraint is that “this is first” always appears before “I'm going second,” while later work can remain concurrent.
13:00
Why a Shared Flag or Mutex Is Not a Signaling Solution
- A shared integer flag with a busy-wait loop may appear to enforce print order, but unsynchronized reads and writes create a data race.
- Adding a mutex around repeated flag checks avoids the race but causes inefficient lock and unlock operations while waiting.
- Using a mutex as a signal is invalid: unlocking an already-unlocked mutex or one owned by another thread is undefined behavior.
19:00
Thread-Safe Output and the Cost of Joining Early
- Multiple writes from separate threads can interleave, even when each thread issues its own writes in a fixed order.
- A mutex around a group of writes can keep output lines together, but library functions should be checked for MT-Safe status in their man pages.
- Joining the first thread before creating the second forces sequential execution and removes the parallelism the program was meant to preserve.
23:00
Semaphore Operations, Initial Values, and POSIX API
- A semaphore is a nonnegative integer shared by threads, optionally shared between processes.
- sem_wait atomically decrements the value and blocks at zero; sem_post atomically increments it.
- POSIX semaphores use sem_t and the semaphore.h header, with sem_init taking a pshared flag and an initial unsigned value.
- sem_trywait is the nonblocking alternative; semaphore operations generally return zero on success.
26:30
Enforcing Ordered Print with a Zero-Count Semaphore
- To keep the second message from printing early, place sem_wait immediately before its print and initialize the semaphore to zero.
- Place sem_post after the first message, so the second thread blocks until the first reaches the required point.
- A zero initial value means no wait can proceed until a post occurs, while unrelated code after the synchronization point can still run concurrently.
34:00
Chaining Three Messages and Choosing Mutexes Versus Semaphores
- To order three messages, use one semaphore for the first-to-second relationship and a second semaphore for the second-to-third relationship.
- A semaphore initialized to one can act like a mutex when paired as wait and post, but its behavior depends on maintaining that initial count.
- Semaphores have no thread owner, unlike mutexes, making them suitable for cross-thread signaling.
- Use a mutex for mutual exclusion and data-race prevention, and a semaphore for ordering.
39:30
Bounded Circular Buffer: Producer and Consumer Requirements
- The example uses a four-slot circular buffer, two producer threads, two consumer threads, and six items per thread.
- Producers fill slots after simulated computation; consumers read and process them, also with simulated computation.
- Consumers must not read empty slots, and producers must not overwrite data that has not yet been consumed.
- The slot operations are assumed thread-safe so the example can focus on ordering and capacity.
45:00
Counting Filled Slots to Block Premature Consumption
- The first observed failure is a consumer emptying a slot before any producer has filled it.
- Place sem_wait before emptying a slot and initialize the filled-slots semaphore to zero.
- Each producer calls sem_post after filling a slot, allowing a consumer to proceed only when data is available.
- This fixes empty-slot reads but does not stop producers from filling slots that are already full.
49:00
Counting Empty Slots to Prevent Producer Overwrites
- Place a second sem_wait before producers fill slots, and initialize the empty-slots semaphore to the buffer size, four.
- After a consumer empties a slot, it posts to the empty-slots semaphore to signal available capacity.
- The two counting semaphores prevent both consuming from an empty buffer and overwriting unconsumed data.
- A full buffer blocks producers until a consumer frees a slot, while consumers drain remaining data after production ends.
55:00
Protecting Buffer Indices and Avoiding Deadlock
- Shared producer and consumer indices still require mutex protection; the example uses a mutex for index updates and wrapping.
- Separate mutexes can protect the producer and consumer indices because each index is shared by only its corresponding group.
- Initializing both filled-slots and empty-slots semaphores to zero deadlocks the system because every thread blocks before any thread can post.
- The central distinction is to use mutexes for data-race prevention and semaphores for ordering and resource counts.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, Jon Eyolfson.