The basics of what an RTOS is and its use of threads was explained in a previous post. This post is more focused on adding more protection to the code to avoid significant issues like race hazards that can be very common when using multiple threads which are trying to use shared resources. Also discussed are semaphores and their use in signalling from an interrupt to a task. A method of preventing race hazards is to use mutexes.
Mutexs and Semaphores
In an RTOS, a mutex is used by a thread to lock a resource while it is being used by that thread and only that thread which locked the mutex can unlock it. stdio_mutex.lock(); is used to lock a resource and stdio_mutex.unlock(); is used to unlock the locked resource, assuming the mutex is declared as Mutex stdio_mutex;. By locking a shared resource, only the thread which locked it can change the values of the shared resource. So if a thread locks a resource and is taken off the processor, that resource stays locked. If another thread then tries to access the resource, the RTOS will not have the mutex (key) available so the thread will be put to one side by the RTOS. When the thread that locked the mutex is put back on the processor, it will unlock the mutex and be taken back of the processor. The RTOS will then run the next thread that was wiating for the mutex. This thread locks the resource and the cycle continues. By locking a shared resource when it is in use, there are no longer any race hazards caused by two threads trying to use a resource at the same time.
Semaphores are often mistakenly considered to be just like having a mutex based on whether the semaphore is locked or unlocked. If it is a locking semaphore then it does behave in the same manner as a mutex where the thread is stopped from operating when waiting for a mutex. Typically a semaphore is used as unlocked where it other tasks will not be blocked from running if a resource is not available. So a semaphore is used when you want to have a shared resource but if it is not available the thread will run other operations. There are issues that appear when using semaphores such as priority inversion and deadlocks.
Priority inversion is where a high priority task is not run due to a lower priority task controlling the semaphore. If the lower priority task isn't given time on the processor then it can't free up the semaphore and the higher priority task is starved of processor time. Therefore the highest priority task is not run as often as desired and drops in priority.
Deadlock is where there are two shared resources that threads want access to. So thread one runs and gets the semaphore for resource 1 but gets kicked off the processor before getting the semaphore for resource 2. Thread two runs and asks for the semaphore for resource 2 first and then tries to get the semaphore for resource 1 which is not available so the thread is parked to wait for that semaphore. The first thread then runs again and looks for the semaphore for resource 2. It is not available so it is also parked to wait for it. Now both threads are waiting for a semaphore held by the other thread so they never run. Preventing this means that all threads must be written to ask for resources in the same order.
mbed RTOS Examples
The codes below show examples of mutexes and semaphores. The mutex code shares an LED resource where one thread turns on the LED while the other turns it off. The first thread turns it on and locks it on. The second can't turn it off until the first is run again to free up the mutex. The semaphores code shares the printf resource with two threads at a time.