Why use Mutexes?

Mutexes are a technique for concurrency management. They are called Mutex because of their MUTual EXclusion property (Only one thread can be doing work at a given time).

Mutexes are used to prevent race conditions on shared data between threads. Lets look at a stack backed by an array. At some point it could look like this:

If we want to insert a value on this stack we need to follow these steps:

1 – Get the index of the head

2 – Increment the index of the head by one

3 – Save a value in the head

If two threads need to insert a value into this stack at the same time, one of the inserted values could get lost:

In the image above, one thread is trying to push 7 to the stack, while another thread is trying to push 4. The end result is that only one of the values is inserted (we don’t know which one), and the other is lost.

How do Mutexes work?

When a thread is about to enter a critical section (Do something that can cause race conditions), it needs to grab a lock of the Mutex. Only one thread can hold the lock at a time. If two threads try to grab the lock at the same time, only one will get it and the other will wait until the other thread releases it before proceeding.

This solves the problem of a race condition, by making sure one operation is performed before the other.

You might be asking yourself: What about a race condition grabbing the lock?. This is avoided at the hardware level by using compare-and-swap. Basically, it can atomically check that the lock has one value (available) and then set it to unavailable. I’m not going to cover how this is done at the hardware level in this post.

The other thing that might be concerning about the picture above is the waiting happening in T2. This part is implemented by the Operating System. When a thread is waiting for a Mutex it won’t be scheduled to do work until the Mutex is released. When the Mutex is finally released, the thread will be woken up and will be able to grab the lock.

C++

Using Mutexes is pretty easy. Here is a naive example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>
#include <mutex>

int value = 0;
std::mutex mutex;

void setValue(int v) {
  mutex.lock();
  value = v;
  mutex.unlock();
}

int main() {
  std::thread t1(setValue, 1);
  std::thread t2(setValue, 2);

  t1.join();
  t2.join();

  std::cout << value << "\n";
}

The example above shows how you can lock a Mutex before accessing shared data and unlock it when you are done. One important thing to notice here is that we have to explicitly unlock the Mutex, otherwise it will never be made available again and the program will be waiting forever. To make things trickier, if the section of code protected by the Mutex threw an exception, the Mutex would never be unlocked.

To prevent problems caused by not unlocking a Mutex, C++ provides std::lock_guard. It works by locking the provided Mutex on creation and unlocking it on it’s destructor (when it goes out of scope).

The previous example refactored to use std::lock_guard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>
#include <mutex>

int value = 0;
std::mutex mutex;

void setValue(int v) {
  std::lock_guard<std::mutex> g(mutex);
  value = v;
}

int main() {
  std::thread t1(setValue, 1);
  std::thread t2(setValue, 2);

  t1.join();
  t2.join();

  std::cout << value << "\n";
}

We saved ourselves the need to call unlock and made the code safer because the Mutex will be unlocked even if an exception is thrown.

[ c++  design_patterns  linux  programming  ]
Configuring ESP32 to act as Access Point
Aligned and packed data in C and C++
ESP32 Non-Volatile Storage (NVS)
Making HTTP / HTTPS requests with ESP32
Modularizing ESP32 Software