C++

Lesson 09

Scoped locks for shared state

Use scoped lock objects so mutex ownership follows block lifetime and early returns cannot leave shared state locked.

Good Code

src/review_cache.cpp
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>

class ReviewCache {
public:
    std::optional<Review> find(const std::string& id) const
    {
        // scoped_lock ties the mutex lifetime to this read.
        std::scoped_lock lock(mutex_);
        auto found = reviews_.find(id);
        if (found == reviews_.end()) {
            return std::nullopt;
        }

        return found->second;
    }

private:
    mutable std::mutex mutex_;
    std::unordered_map<std::string, Review> reviews_;
};

Bad Code

review_cache.cpp
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>

class ReviewCache {
public:
    std::optional<Review> find(const std::string& id) const
    {
        mutex_.lock();
        auto found = reviews_.find(id);
        if (found == reviews_.end()) {
            // Early return leaves the mutex locked.
            return std::nullopt;
        }

        mutex_.unlock();
        return found->second;
    }

private:
    mutable std::mutex mutex_;
    std::unordered_map<std::string, Review> reviews_;
};

Review Notes

What to review

Good Code

The good version creates a lock object at the top of the critical section. The mutex unlocks when the lock leaves scope.

Bad Code

The bad version calls lock() and unlock() manually. The not-found branch returns before unlock, so later calls can deadlock.

Takeaways

  • Mutex code should make the lock lifetime visible in scope, not depend on matching manual lock and unlock calls.