CodeNebula
C++14stlintermediate

std::shared_lock

C++14 · Modern Refined · Compiler flag: -std=c++14

Explanation

An RAII lock guard that acquires shared (read) ownership of a `SharedMutex` (typically `std::shared_timed_mutex`). It is the shared-ownership counterpart of `std::unique_lock` and supports deferred locking, timed locking, and manual lock/unlock, all with automatic release on destruction.

Real-World Scenario

A DNS cache is read by many resolver threads concurrently. Each reader acquires a `std::shared_lock` for the duration of its lookup, while cache updates use a `std::unique_lock` for exclusive access.

Quick Snippet

snippet.cpp
#include <shared_mutex>
#include <map>
#include <string>
#include <thread>
#include <iostream>
#include <vector>

class DnsCache {
    mutable std::shared_timed_mutex mtx_;
    std::map<std::string, std::string> cache_;
public:
    std::string resolve(const std::string& host) const {
        std::shared_lock<std::shared_timed_mutex> lock(mtx_); // shared read
        auto it = cache_.find(host);
        return it != cache_.end() ? it->second : "NOT_FOUND";
    }

    void update(const std::string& host, const std::string& ip) {
        std::unique_lock<std::shared_timed_mutex> lock(mtx_); // exclusive write
        cache_[host] = ip;
    }
};

int main() {
    DnsCache cache;
    cache.update("example.com", "93.184.216.34");

    std::vector<std::thread> readers;
    for (int i = 0; i < 4; ++i)
        readers.emplace_back([&cache, i] {
            std::cout << "Thread " << i << ": "
                      << cache.resolve("example.com") << "\n";
        });
    for (auto& t : readers) t.join();
}

Full Example

shared_lock.cpp
// CodeNebula - C++14 Example: std::shared_lock
// Compile: g++ -std=c++14 -Wall -Wextra -pthread -o example shared_lock.cpp

// Demonstrates std::shared_lock for acquiring shared (read) ownership of a
// shared_timed_mutex. While unique_lock provides exclusive access, shared_lock
// allows multiple threads to read concurrently. Real-world use: thread-safe
// read-heavy data structures, DNS caches, routing tables, and config stores.

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

// ------------------------------------------------------------------
// Thread-safe print helper
// ------------------------------------------------------------------
std::mutex print_mutex;

template<typename... Args>
void safe_print(Args&&... args)
{
    std::lock_guard<std::mutex> lock(print_mutex);
    // Initializer list trick for expansion
    using swallow = int[];
    (void)swallow{0, (void(std::cout << std::forward<Args>(args)), 0)...};
    std::cout << std::endl;
}

// ------------------------------------------------------------------
// Thread-safe DNS cache using shared_lock (readers) / unique_lock (writers)
// ------------------------------------------------------------------
class DnsCache
{
    mutable std::shared_timed_mutex mutex_;
    std::map<std::string, std::string> entries_;

public:
    // Read: multiple threads can resolve simultaneously
    std::string resolve(const std::string& hostname) const
    {
        std::shared_lock<std::shared_timed_mutex> lock(mutex_);
        auto it = entries_.find(hostname);
        if (it != entries_.end())
            return it->second;
        return "(not found)";
    }

    // Write: exclusive access to update an entry
    void update(const std::string& hostname, const std::string& ip)
    {
        std::unique_lock<std::shared_timed_mutex> lock(mutex_);
        entries_[hostname] = ip;
    }

    // Read: get current size
    std::size_t size() const
    {
        std::shared_lock<std::shared_timed_mutex> lock(mutex_);
        return entries_.size();
    }

    // Read: dump all entries
    std::string dump() const
    {
        std::shared_lock<std::shared_timed_mutex> lock(mutex_);
        std::ostringstream oss;
        for (const auto& e : entries_)
            oss << "    " << e.first << " -> " << e.second << "\n";
        return oss.str();
    }
};

int main()
{
    DnsCache cache;

    // ------------------------------------------------------------------
    // 1. Populate the cache (single-threaded setup)
    // ------------------------------------------------------------------
    std::cout << "=== Populating DNS Cache ===" << std::endl;
    cache.update("example.com", "93.184.216.34");
    cache.update("google.com", "142.250.80.46");
    cache.update("github.com", "140.82.121.4");
    std::cout << "  Entries: " << cache.size() << std::endl;
    std::cout << cache.dump() << std::endl;

    // ------------------------------------------------------------------
    // 2. Multiple concurrent readers (shared_lock allows parallel reads)
    // ------------------------------------------------------------------
    std::cout << "=== Concurrent DNS Lookups ===" << std::endl;
    std::vector<std::thread> readers;
    std::vector<std::string> hosts = {"example.com", "google.com", "github.com", "unknown.org"};

    for (const auto& host : hosts)
    {
        readers.emplace_back([&cache, host]() {
            std::string ip = cache.resolve(host);
            safe_print("  resolve(", host, ") = ", ip);
        });
    }
    for (auto& t : readers) t.join();
    readers.clear();
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // 3. shared_lock features: deferred locking
    // ------------------------------------------------------------------
    std::cout << "=== Deferred Locking ===" << std::endl;
    {
        std::shared_timed_mutex mtx;

        std::shared_lock<std::shared_timed_mutex> lock(mtx, std::defer_lock);
        std::cout << "  Lock created (deferred): owns_lock = "
                  << std::boolalpha << lock.owns_lock() << std::endl;

        lock.lock();
        std::cout << "  After lock():            owns_lock = "
                  << lock.owns_lock() << std::endl;

        lock.unlock();
        std::cout << "  After unlock():          owns_lock = "
                  << lock.owns_lock() << std::endl;
    }
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // 4. Readers and writers working together
    // ------------------------------------------------------------------
    std::cout << "=== Readers + Writer Concurrency ===" << std::endl;

    // Writer thread adds new entries
    std::thread writer([&cache]() {
        cache.update("rust-lang.org", "13.225.255.27");
        safe_print("  [writer] Added rust-lang.org");
        cache.update("cppreference.com", "104.26.1.93");
        safe_print("  [writer] Added cppreference.com");
    });

    // Reader threads perform lookups concurrently
    std::vector<std::thread> lookup_threads;
    for (int i = 0; i < 3; ++i)
    {
        lookup_threads.emplace_back([&cache, i]() {
            // Small delay so some reads happen after writes
            std::this_thread::sleep_for(std::chrono::milliseconds(i * 5));
            std::string id = "reader" + std::to_string(i);
            safe_print("  [", id, "] google.com -> ", cache.resolve("google.com"));
        });
    }

    writer.join();
    for (auto& t : lookup_threads) t.join();
    std::cout << std::endl;

    // ------------------------------------------------------------------
    // 5. Final state
    // ------------------------------------------------------------------
    std::cout << "=== Final Cache State ===" << std::endl;
    std::cout << "  Total entries: " << cache.size() << std::endl;
    std::cout << cache.dump();

    return 0;
}

/*
Expected output (thread ordering may vary):

=== Populating DNS Cache ===
  Entries: 3
    example.com -> 93.184.216.34
    github.com -> 140.82.121.4
    google.com -> 142.250.80.46

=== Concurrent DNS Lookups ===
  resolve(example.com) = 93.184.216.34
  resolve(google.com) = 142.250.80.46
  resolve(github.com) = 140.82.121.4
  resolve(unknown.org) = (not found)

=== Deferred Locking ===
  Lock created (deferred): owns_lock = false
  After lock():            owns_lock = true
  After unlock():          owns_lock = false

=== Readers + Writer Concurrency ===
  [writer] Added rust-lang.org
  [writer] Added cppreference.com
  [reader0] google.com -> 142.250.80.46
  [reader1] google.com -> 142.250.80.46
  [reader2] google.com -> 142.250.80.46

=== Final Cache State ===
  Total entries: 5
    cppreference.com -> 104.26.1.93
    example.com -> 93.184.216.34
    github.com -> 140.82.121.4
    google.com -> 142.250.80.46
    rust-lang.org -> 13.225.255.27
*/
Run command
g++ -std=c++14 -Wall -Wextra -pthread -o example shared_lock.cpp