Ежедневный бит (е) C++ # 15, мьютекс с общими блокировками: std::shared_mutex

std::shared_mutex — это вариант std::mutex C++14 (C++17 для версии без синхронизации) std::mutex, который поддерживает два типа блокировок: монопольную блокировку, которая может удерживаться только одним потоком, и общую блокировку, которая может удерживаться любым потоком. количество потоков (пока эксклюзивная блокировка не удерживается).

Это семантика, обычно необходимая для доступа для чтения и записи, поскольку операции чтения могут выполняться параллельно, а операции записи требуют монопольного доступа.

#include <mutex>
#include <shared_mutex>
#include <array>
#include <optional>

struct Data {};
struct RecentSnapshots {
    void push(const Data& data) {
        // We are about to modify the data, grab a unique_lock
        std::unique_lock lock(mux_);
        buffer_[offset_ % 64] = data;
        ++offset_;
    }
    std::optional<Data> get(size_t index) const {
        // We only read, but need to prevent 
        // concurrent writes, grab a shared_lock
        std::shared_lock lock(mux_);
        if (index >= offset_)
            return std::nullopt;
        if (offset_ >= 64 && offset_-64 > index)
            return std::nullopt;
        return buffer_[index % 64];
    }
    size_t min_offset() const {
        // We only read, but need to prevent 
        // concurrent writes, grab a shared_lock
        std::shared_lock lock(mux_);
        if (offset_ <= 64) return 0;
        return offset_ - 64;
    }
private:
    // We need mutable, since we mutate the state
    // of this mutex (by grabbing a lock) in const methods.
    mutable std::shared_mutex mux_;
    std::array<Data,64> buffer_;
    size_t offset_ = 0;
};

// Note: calling min_offset() followed by get(offset)
// does NOT provide any transactionality, as a write can interject
// itself between the two calls.

Откройте этот пример в Compiler Explorer.