<shared_mutex>
The <shared_mutex>
header is part of the C++ Standard Library, introduced in C++17. It provides shared mutex types that allow multiple readers to access a resource simultaneously while ensuring exclusive access for writers. This header is particularly useful for implementing read-write locks, which can significantly improve performance in scenarios where reads are more frequent than writes.
std::shared_mutex
and std::shared_timed_mutex
classesstd::shared_lock
for RAII-style management of shared ownershipstd::shared_mutex
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <chrono>
class ThreadSafeCounter {
private:
mutable std::shared_mutex mutex_;
int value_ = 0;
public:
int get() const {
std::shared_lock lock(mutex_);
return value_;
}
void increment() {
std::unique_lock lock(mutex_);
value_++;
}
};
int main() {
ThreadSafeCounter counter;
auto reader = [&counter](int id) {
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Reader " << id << " sees value " << counter.get() << std::endl;
}
};
auto writer = [&counter]() {
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
counter.increment();
std::cout << "Writer incremented value\n";
}
};
std::vector<std::thread> threads;
threads.emplace_back(writer);
for (int i = 0; i < 3; ++i) {
threads.emplace_back(reader, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
ThreadSafeCounter
class using std::shared_mutex
.get()
uses std::shared_lock
for read access, allowing multiple concurrent readers.increment()
uses std::unique_lock
for write access, ensuring exclusive access.#include <iostream>
#include <unordered_map>
#include <shared_mutex>
#include <thread>
#include <vector>
class ThreadSafeCache {
private:
mutable std::shared_mutex mutex_;
std::unordered_map<int, std::string> cache_;
public:
std::string get(int key) const {
std::shared_lock lock(mutex_);
auto it = cache_.find(key);
if (it != cache_.end()) {
return it->second;
}
return "Not found";
}
void set(int key, const std::string& value) {
std::unique_lock lock(mutex_);
cache_[key] = value;
}
};
int main() {
ThreadSafeCache cache;
auto reader = [&cache](int id) {
for (int i = 0; i < 5; ++i) {
std::cout << "Reader " << id << " gets: " << cache.get(i % 3) << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
};
auto writer = [&cache](int id) {
for (int i = 0; i < 3; ++i) {
cache.set(i, "Value " + std::to_string(id) + "-" + std::to_string(i));
std::cout << "Writer " << id << " set value for key " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
};
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back(writer, i);
}
for (int i = 0; i < 5; ++i) {
threads.emplace_back(reader, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
std::shared_mutex
.get()
uses shared locking to allow concurrent reads.set()
uses exclusive locking to ensure safe writes.std::shared_timed_mutex
with Timeouts#include <iostream>
#include <shared_mutex>
#include <thread>
#include <chrono>
class TimedResource {
private:
mutable std::shared_timed_mutex mutex_;
int value_ = 0;
public:
bool try_read(std::chrono::milliseconds timeout) const {
if (mutex_.try_lock_shared_for(timeout)) {
std::cout << "Read value: " << value_ << std::endl;
mutex_.unlock_shared();
return true;
}
return false;
}
bool try_write(int new_value, std::chrono::milliseconds timeout) {
if (mutex_.try_lock_for(timeout)) {
value_ = new_value;
std::cout << "Wrote value: " << value_ << std::endl;
mutex_.unlock();
return true;
}
return false;
}
};
int main() {
TimedResource resource;
auto reader = [&resource](int id) {
for (int i = 0; i < 3; ++i) {
if (resource.try_read(std::chrono::milliseconds(50))) {
std::cout << "Reader " << id << " succeeded\n";
} else {
std::cout << "Reader " << id << " timed out\n";
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
};
auto writer = [&resource](int id) {
for (int i = 0; i < 2; ++i) {
if (resource.try_write(id * 10 + i, std::chrono::milliseconds(200))) {
std::cout << "Writer " << id << " succeeded\n";
} else {
std::cout << "Writer " << id << " timed out\n";
}
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
};
std::vector<std::thread> threads;
threads.emplace_back(writer, 1);
threads.emplace_back(writer, 2);
for (int i = 0; i < 3; ++i) {
threads.emplace_back(reader, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
std::shared_timed_mutex
to implement timed locking operations.try_lock_shared_for()
is used for timed shared (read) locking.try_lock_for()
is used for timed exclusive (write) locking.Performance: Shared mutexes can improve performance in read-heavy scenarios but may have more overhead than simple mutexes for write operations.
Deadlock Prevention: Be cautious when using multiple locks to avoid deadlocks. Always acquire locks in a consistent order.
Upgrade Locks: C++ doesn't provide a direct way to upgrade a shared lock to an exclusive lock without releasing and re-acquiring, which can lead to race conditions.
Compatibility: <shared_mutex>
is available from C++17 onwards. For earlier standards, consider using <shared_mutex>
from C++14 or boost::shared_mutex
.
Reader Preference: Most implementations of shared mutexes have a reader preference, which might lead to writer starvation in extreme cases.
The <shared_mutex>
header in C++ provides powerful tools for implementing read-write locks:
std::shared_mutex
and std::shared_timed_mutex
for shared ownership semantics.std::shared_lock
for RAII-style management of shared locks.Shared mutexes are a valuable addition to C++'s concurrency toolkit, offering a middle ground between the simplicity of standard mutexes and the complexity of more advanced synchronization primitives. They are especially beneficial in improving performance for read-heavy workloads where traditional mutexes might unnecessarily serialize read access.
However, it's important to use shared mutexes judiciously. They come with added complexity and potential for subtle bugs if not used correctly. In scenarios where reads and writes are equally frequent, or where the critical sections are very short, standard mutexes might still be more appropriate due to their simplicity and lower overhead.
Understanding and effectively using <shared_mutex>
can lead to more efficient and scalable concurrent C++ programs, particularly in systems where read operations significantly outnumber write operations.