Mutexes in C++

In this example, we’ll explore how to use mutexes to safely access data across multiple threads. This is useful for managing more complex state than can be handled by atomic operations.

#include <iostream>
#include <map>
#include <mutex>
#include <thread>
#include <vector>

// Container holds a map of counters; since we want to
// update it concurrently from multiple threads, we
// add a mutex to synchronize access.
class Container {
private:
    std::mutex mu;
    std::map<std::string, int> counters;

public:
    // Lock the mutex before accessing counters; unlock
    // it at the end of the function using a lock_guard
    // for exception safety.
    void inc(const std::string& name) {
        std::lock_guard<std::mutex> lock(mu);
        ++counters[name];
    }

    // Method to get the current state of counters
    std::map<std::string, int> getCounters() {
        std::lock_guard<std::mutex> lock(mu);
        return counters;
    }
};

int main() {
    // Note that the mutex is automatically initialized
    Container c;
    c.inc("a"); // Initialize "a" to 1
    c.inc("b"); // Initialize "b" to 1

    // This function increments a named counter
    // in a loop.
    auto doIncrement = [&c](const std::string& name, int n) {
        for (int i = 0; i < n; ++i) {
            c.inc(name);
        }
    };

    // Run several threads concurrently; note
    // that they all access the same Container,
    // and two of them access the same counter.
    std::vector<std::thread> threads;
    threads.emplace_back(doIncrement, "a", 9999);
    threads.emplace_back(doIncrement, "a", 9999);
    threads.emplace_back(doIncrement, "b", 9999);

    // Wait for the threads to finish
    for (auto& t : threads) {
        t.join();
    }

    // Print the final state of the counters
    for (const auto& pair : c.getCounters()) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

Running the program shows that the counters are updated as expected:

$ g++ -std=c++11 -pthread mutexes.cpp -o mutexes
$ ./mutexes
a: 20000
b: 10000

In this C++ version:

  1. We use std::mutex for synchronization and std::lock_guard for exception-safe locking.
  2. The Container class encapsulates the mutex and the map of counters.
  3. We use a lambda function for doIncrement, capturing the container by reference.
  4. Threads are created using std::thread and joined at the end of main().
  5. The getCounters() method is added to safely access the final state of the counters.

This example demonstrates how to use mutexes to safely manage shared state across multiple threads in C++. Next, we could explore implementing similar functionality using only threads and message passing.