ConcurrencyC++verifiedVerified
Mutex / Lock Pattern in C++
Guarantee that only one thread at a time can access a shared resource by requiring threads to acquire an exclusive lock before proceeding.
How to Implement the Mutex / Lock Pattern in C++
1Step 1: Shared resource protected by a mutex
class Counter {
int value_ = 0;
std::mutex mu_;
public:
void increment() {
std::lock_guard lock(mu_);
++value_;
}
int get() const { return value_; }
};2Step 2: Multiple threads safely increment the counter
int main() {
Counter counter;
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&counter] {
for (int j = 0; j < 1000; ++j)
counter.increment();
});
}
for (auto& t : threads) t.join();
std::cout << "Final count: " << counter.get() << "\n";
// Always 10000 thanks to mutex protection
}#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <vector>
#include <map>
#include <format>
#include <chrono>
#include <string>
#include <stdexcept>
// [step] Thread-safe map with shared_mutex (read-write lock)
template <typename K, typename V>
class ConcurrentMap {
std::map<K, V> data_;
mutable std::shared_mutex mu_;
public:
// Multiple readers allowed simultaneously
std::optional<V> get(const K& key) const {
std::shared_lock lock(mu_);
auto it = data_.find(key);
return it != data_.end() ? std::optional<V>{it->second} : std::nullopt;
}
// Exclusive access for writes
void set(const K& key, V value) {
std::unique_lock lock(mu_);
data_[key] = std::move(value);
}
bool erase(const K& key) {
std::unique_lock lock(mu_);
return data_.erase(key) > 0;
}
size_t size() const {
std::shared_lock lock(mu_);
return data_.size();
}
// [step] Scoped update: read-modify-write atomically
template <typename Fn>
void update(const K& key, Fn updater) {
std::unique_lock lock(mu_);
auto it = data_.find(key);
if (it != data_.end()) {
updater(it->second);
}
}
// Snapshot for iteration (avoids holding lock during processing)
std::map<K, V> snapshot() const {
std::shared_lock lock(mu_);
return data_;
}
};
// [step] Demonstrate deadlock-free locking with std::scoped_lock
class BankAccount {
double balance_;
mutable std::mutex mu_;
std::string name_;
public:
BankAccount(std::string name, double balance)
: name_(std::move(name)), balance_(balance) {}
// Transfer uses std::scoped_lock to prevent deadlocks
static void transfer(BankAccount& from, BankAccount& to, double amount) {
// scoped_lock acquires both mutexes without deadlock risk
std::scoped_lock lock(from.mu_, to.mu_);
if (from.balance_ < amount)
throw std::runtime_error(
std::format("{} has insufficient funds", from.name_));
from.balance_ -= amount;
to.balance_ += amount;
}
double balance() const {
std::lock_guard lock(mu_);
return balance_;
}
const std::string& name() const { return name_; }
};
// [step] Demonstrate concurrent operations
int main() {
// Concurrent map
ConcurrentMap<std::string, int> metrics;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back([&metrics, i] {
for (int j = 0; j < 100; ++j) {
auto key = std::format("metric-{}", j % 10);
metrics.set(key, i * 100 + j);
}
});
}
// Concurrent readers
for (int i = 0; i < 3; ++i) {
threads.emplace_back([&metrics] {
for (int j = 0; j < 50; ++j) {
auto val = metrics.get(std::format("metric-{}", j % 10));
}
});
}
for (auto& t : threads) t.join();
std::cout << std::format("Map size: {}\n", metrics.size());
// Bank account transfers (deadlock-free)
BankAccount alice("Alice", 1000);
BankAccount bob("Bob", 1000);
std::vector<std::thread> transfers;
for (int i = 0; i < 100; ++i) {
transfers.emplace_back([&] {
BankAccount::transfer(alice, bob, 1.0);
});
transfers.emplace_back([&] {
BankAccount::transfer(bob, alice, 1.0);
});
}
for (auto& t : transfers) t.join();
std::cout << std::format("Alice: {:.2f}, Bob: {:.2f}, Total: {:.2f}\n",
alice.balance(), bob.balance(),
alice.balance() + bob.balance());
}Mutex / Lock Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Mutex / Lock Pattern in the Real World
“A single-occupancy public restroom with a door latch is a perfect mutex. The latch (lock) ensures only one person (thread) occupies the restroom (critical section) at a time. Anyone who finds the door locked must wait outside; when the occupant leaves and unlatches the door, one waiting person may enter.”