EduC++ Protecting Shared Data with Mutexes

Protecting Shared Data with Mutexes

Prerequisites: std::thread basics, RAII pattern, move semantics.
Standard: C++11 introduced std::mutex, lock_guard, unique_lock. C++17 added std::scoped_lock for locking multiple mutexes deadlock-free in a single statement.

Watch out: never lock a std::mutex twice from the same thread -- it's UB. Use std::recursive_mutex if recursion is needed. -----------------------------------------------

class Counter {
    int value_ = 0;
    mutable std::mutex mtx_;

public:
    // Thread-safe increment
    void increment() {
        std::lock_guard<std::mutex> lock(mtx_);  // RAII lock
        ++value_;
        // lock automatically released here
    }

    // Thread-safe read
    int get() const {
        std::lock_guard<std::mutex> lock(mtx_);
        return value_;
    }
};

2. Bank account with scoped_lock for two-mutex locking

scoped_lock) -- never raw .lock()/.unlock(). Manual lock/unlock is
easy to get wrong on exceptions or early returns.

Watch out: always use RAII wrappers (lock_guard, unique_lock,

class BankAccount {
    double balance_ = 0;
    std::mutex mtx_;

public:
    explicit BankAccount(double initial) : balance_(initial) {}

    void transfer_to(BankAccount& other, double amount) {
        // Lock both accounts to prevent deadlock
        std::scoped_lock lock(mtx_, other.mtx_);  // C++17

        if (balance_ >= amount) {
            balance_ -= amount;
            other.balance_ += amount;
            std::cout << std::format("Transferred {:.2f}\n", amount);
        }
    }

    double balance() const { return balance_; }
};

int main() {
    // Counter example
    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 << std::format("Final count: {} (expected: 10000)\n",
                             counter.get());

    // Bank transfer example
    BankAccount alice(1000), bob(500);

    std::thread t1([&] { alice.transfer_to(bob, 100); });
    std::thread t2([&] { bob.transfer_to(alice, 50); });

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

    std::cout << std::format("Alice: {:.2f}, Bob: {:.2f}\n",
                             alice.balance(), bob.balance());

    return 0;
}