I understand that mutexes and semaphores are used to protect shared resources, but their practical applications still puzzle me. While I understand their theoretical purposes, the examples in textbooks often appear simplified. Could someone provide a real-world example that illustrate and clarifies when to choose between a mutex and a semaphore.
Personally, I use a Mutex when I am using the object for the protection of a shared resource.
Note, a Mutex IS a “Semaphore”, just a specialized version that adds restrictions on how it is used, and in return gets the ability to have some additional functionality.
The limitation on a Mutex is that it can only be given back by the same task that took it, and because of that, this task can be said to “own” the Mutex while it holds the “lock”. This allows the OS code to elevate the priority of that task, if a higher-priority task tries to take the lock while it is being held (avoiding a “priority inversion” if a task between the priorities of those two tasks is running).
A semaphore doesn’t have that same access restriction, ANY task can “give” the semaphore, and as such we don’t have the same concept of “ownership”, so we can’t implement the same priority inheritance.
What I use semaphores more for is to allow a task to wait for some event to happen, and when that happens, somebody (be it a task or an ISR) will then give the semaphore, so the waiting task can take it. Probably my most common usage here is waiting for an I/O operation to complete, the task calls a driver function which starts up the I/O and then waits for a semaphore, which will be set by the ISR part of the driver when the operation is complete. Said driver may also have a Mutex, to protect itself from being used by two tasks at once.