Instiq
Chapter 3 · Real-time OS & task management·v1.0.0·Updated 7/10/2026·~16 min

What's changed: Initial version

3.3Inter-task synchronization and communication

Key points

Covers the difference in purpose between a semaphore and a mutex for mutual exclusion (counting resource management versus exclusive ownership), event flags for signaling that an event occurred, and the difference between a mailbox and a message queue for passing data—together with judging which primitive to choose for a given inter-task coordination scenario.

In an embedded system where multiple tasks share resources or exchange information, which synchronization or communication primitive you choose determines whether bugs creep in. A careless choice—"just use a semaphore for mutual exclusion"—can lead to serious problems such as priority inversion (covered later). This section precisely distinguishes the intended purpose of each primitive and how to choose among them in practice.

3.3.1The difference between semaphores and mutexes

  • A semaphore (counting semaphore) acts as a counter tracking the remaining number of a resource. take (P operation) decrements the counter and blocks if it would go below zero; give (V operation) increments it. It suits cases where multiple tasks, up to some count, may use the resource concurrently (e.g., managing a pool of 3 free buffers), or where an interrupt handler signals a wake-up to a task. A binary semaphore (counter capped at 1) can be used for simple exclusion, but it has no notion of ownership.
  • A mutex (mutual exclusion) is a synchronization primitive dedicated to exclusive locking that carries the notion of ownership—who currently holds the resource. Only the task that acquired it may release it (the acquirer and releaser must be the same task), and it is precisely this ownership information that makes it possible to implement the priority inheritance protocol discussed later. A semaphore, being a plain counter with no ownership concept, cannot support priority inheritance.
  • The practical rule of thumb: use a mutex for mutual exclusion (only one task may use the resource), and a semaphore for counting a resource pool or for wake-up signaling from an interrupt. Repurposing a binary semaphore for mutual exclusion that needs protection against priority inversion is a classic design mistake.

3.3.2Event flags, mailboxes, and message queues

  • Event flags represent the occurrence or non-occurrence of multiple events using multiple bits. A single task can wait for an AND (all conditions met) or an OR (any one condition met) of several conditions, expressing compound conditions more efficiently than waiting on several separate semaphores individually.
  • A mailbox can hold only a single message (typically one pointer's worth) at a time—a simple, single-slot data-handoff mechanism. In many implementations, the previous message must be read before the next one arrives.
  • A message queue provides a buffer that can hold multiple messages in FIFO order. When the sender and receiver process at uneven rates (bursty data), the queue acts as a buffer that absorbs the mismatch and prevents dropped messages. Sizing the queue capacity (queue depth) correctly is an important design decision.
Exam point

Most-tested: "a mutex carries ownership and only the acquirer can release it (a prerequisite for implementing priority inheritance)," "a semaphore is a resource counter, also used for wake-up signaling from interrupts," and "a mailbox holds only one message, while a message queue holds multiple messages in FIFO order." The point that using a semaphore for mutual exclusion precludes implementing priority inheritance is a frequent target, especially in combination with the priority-inversion topic in the next section.

Suppose a firmware developer is designing Task A, which writes sensor data into a shared buffer, and Task B, which reads and processes that buffer. For the requirement "prevent concurrent access to the shared buffer," a mutex should be chosen. The reason: if you later want to add priority inheritance as a countermeasure against priority inversion on this buffer access, a mutex retains ownership information (who currently holds it) in the RTOS kernel, so the protocol can be applied—whereas a binary semaphore has no notion of ownership and cannot have inheritance retrofitted onto it. For the requirement "wake up Task A when a sensor interrupt fires," a semaphore is used (typically a binary semaphore that an ISR can give)—an interrupt handler does not fit the notion of ownership, so a semaphore is the right fit here. For the communication requirement "pass the sensor value Task A read to Task B," if the sensor can generate several values in a burst over a short time, a mailbox, which holds only one message, would overwrite the previous value (or block Task A until Task B reads it)—so a message queue, which buffers multiple messages in FIFO order, should be chosen instead, with its queue depth sized to the maximum burst count. Conversely, if the sensor always produces exactly one value at a deterministic pace, a simple mailbox suffices. Applying the rule "mutex for exclusion, semaphore for interrupt wake-up, message queue for bursty data hand-off, mailbox for single-item hand-off" to each situation is the judgment expected of an embedded designer.

PrimitivePrimary purposeHas ownership?
MutexMutual exclusion (only one task holds the resource)Yes (required to implement priority inheritance)
SemaphoreCounting a resource pool / wake-up signaling from interruptsNo
Event flagsWaiting on an AND/OR of multiple conditionsNo
MailboxSingle-item data hand-offNo
Message queueMulti-item data hand-off with a FIFO bufferNo
Warning

Trap: "It is essentially the same whether you use a semaphore or a mutex for mutual exclusion" is wrong—a mutex carries ownership and can support priority inheritance, whereas a semaphore has no ownership and cannot have this protection built in, so a mutex should be the default choice for mutual exclusion where priority inversion could be a problem. Also wrong: "a mailbox and a message queue are the same thing"—a mailbox holds only one item, while a message queue buffers multiple items in FIFO order.

Semaphore/mutex/queue.
Coordinating tasks

3.3.3Section summary

  • A mutex is dedicated to mutual exclusion with ownership; a semaphore is a resource counter also used for interrupt wake-up signaling
  • A semaphore, lacking ownership, cannot implement priority inheritance; choose a mutex for exclusion that needs protection against priority inversion
  • A mailbox holds only one item; a message queue buffers multiple items in FIFO order, absorbing bursty data

Sign in to track progress — Log in.

Quick check

(just a quick review)

Q1. Exclusive access to a shared buffer was implemented with a binary semaphore, and later the team wants to add priority inheritance as a countermeasure against priority inversion. What design problem becomes apparent at this point?

Q2. A sensor can generate multiple values in a burst over a short time, and the processing task sometimes reads them more slowly than they arrive. Which mechanism is most appropriate for passing values from the sensor-reading task to the processing task?

Q3. A task should wake up only once both the "sensor initialization complete" event and the "communication link established" event have occurred. Which synchronization primitive most naturally implements this requirement?

Check your understandingPractice questions for Chapter 3: Real-time OS & task management