CodeNebula
C++17stladvanced

std::shared_mutex

C++17 · Practical Modern · Compiler flag: -std=c++17

Explanation

A mutex that supports both exclusive (write) and shared (read) locking. Multiple threads can hold a shared lock simultaneously for read access, but exclusive locks block all other access. This enables the classic readers-writer lock pattern.

Real-World Scenario

Protect a configuration store that is read frequently by many threads but updated rarely by an admin thread.

Quick Snippet

snippet.cpp
#include <shared_mutex>
#include <map>
#include <string>

std::shared_mutex rwMutex;
std::map<std::string, int> config;

void readConfig(const std::string& key) {
    std::shared_lock lock(rwMutex);  // shared (read) access
    auto it = config.find(key);
}

void writeConfig(const std::string& key, int value) {
    std::unique_lock lock(rwMutex);  // exclusive (write) access
    config[key] = value;
}

Full Example

shared_mutex.cpp
// CodeNebula - C++17 Example: std::shared_mutex
// Compile: g++ -std=c++17 -Wall -Wextra -o example shared_mutex.cpp -lpthread

// Demonstrates std::shared_mutex, a reader-writer lock that allows multiple
// concurrent readers OR one exclusive writer. Use std::shared_lock for read
// access and std::unique_lock (or std::lock_guard) for write access.

#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>
#include <string>
#include <map>
#include <mutex>
#include <chrono>

// ------------------------------------------------------------------
// Thread-safe cache using shared_mutex
// ------------------------------------------------------------------
class ThreadSafeCache {
    mutable std::shared_mutex mutex_;
    std::map<std::string, int> data_;
    std::mutex cout_mutex_;

    void log(const std::string& msg) {
        std::lock_guard<std::mutex> lock(cout_mutex_);
        std::cout << "  " << msg << std::endl;
    }

public:
    // Read operation: multiple threads can read concurrently
    int get(const std::string& key) const {
        std::shared_lock lock(mutex_);  // Shared (read) lock
        auto it = data_.find(key);
        return (it != data_.end()) ? it->second : -1;
    }

    // Write operation: exclusive access required
    void set(const std::string& key, int value) {
        std::unique_lock lock(mutex_);  // Exclusive (write) lock
        data_[key] = value;
    }

    // Read: check if key exists
    bool contains(const std::string& key) const {
        std::shared_lock lock(mutex_);
        return data_.find(key) != data_.end();
    }

    // Read: get size
    std::size_t size() const {
        std::shared_lock lock(mutex_);
        return data_.size();
    }
};

int main()
{
    ThreadSafeCache cache;

    // ------------------------------------------------------------------
    // 1. Writer threads populate the cache
    // ------------------------------------------------------------------
    std::cout << "=== Writers ===" << std::endl;
    std::vector<std::thread> writers;
    for (int i = 0; i < 4; ++i) {
        writers.emplace_back([&cache, i]() {
            std::string key = "key_" + std::to_string(i);
            cache.set(key, i * 100);
        });
    }
    for (auto& t : writers) t.join();
    std::cout << "  Written " << cache.size() << " entries" << std::endl;

    // ------------------------------------------------------------------
    // 2. Multiple reader threads read concurrently
    // ------------------------------------------------------------------
    std::cout << "\n=== Concurrent Readers ===" << std::endl;
    std::mutex print_mutex;
    std::vector<std::thread> readers;

    for (int i = 0; i < 4; ++i) {
        readers.emplace_back([&cache, &print_mutex, i]() {
            std::string key = "key_" + std::to_string(i);
            int val = cache.get(key);
            std::lock_guard<std::mutex> lock(print_mutex);
            std::cout << "  Reader " << i << ": " << key << " = " << val << std::endl;
        });
    }
    for (auto& t : readers) t.join();

    // ------------------------------------------------------------------
    // 3. Mixed readers and writers
    // ------------------------------------------------------------------
    std::cout << "\n=== Mixed Read/Write ===" << std::endl;
    std::vector<std::thread> mixed;

    // Writer adds new entry
    mixed.emplace_back([&cache]() {
        cache.set("key_new", 999);
    });

    // Readers check for existing data
    for (int i = 0; i < 3; ++i) {
        mixed.emplace_back([&cache, &print_mutex, i]() {
            bool exists = cache.contains("key_" + std::to_string(i));
            std::lock_guard<std::mutex> lock(print_mutex);
            std::cout << "  Reader: key_" << i << " exists=" << exists << std::endl;
        });
    }
    for (auto& t : mixed) t.join();

    std::cout << "\n  Final cache size: " << cache.size() << std::endl;

    // ------------------------------------------------------------------
    // 4. Lock types summary
    // ------------------------------------------------------------------
    std::cout << "\n=== Lock Summary ===" << std::endl;
    std::cout << "  shared_lock  -> shared (read) access, multiple allowed" << std::endl;
    std::cout << "  unique_lock  -> exclusive (write) access, one at a time" << std::endl;
    std::cout << "  lock_guard   -> exclusive access (simpler, non-movable)" << std::endl;

    return 0;
}

/*
Expected output (thread order may vary):

=== Writers ===
  Written 4 entries

=== Concurrent Readers ===
  Reader 0: key_0 = 0
  Reader 1: key_1 = 100
  Reader 2: key_2 = 200
  Reader 3: key_3 = 300

=== Mixed Read/Write ===
  Reader: key_0 exists=1
  Reader: key_1 exists=1
  Reader: key_2 exists=1

  Final cache size: 5

=== Lock Summary ===
  shared_lock  -> shared (read) access, multiple allowed
  unique_lock  -> exclusive (write) access, one at a time
  lock_guard   -> exclusive access (simpler, non-movable)
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example shared_mutex.cpp -lpthread