I need to make a thread-safe map, where I mean that each value must be independently mutexed. For example, I need to be able to get map["abc"] and map["vf"] at the same time from 2 different threads.
My idea is to make two maps: one for data and one for mutex for every key:
class cache
{
private:
....
std::map<std::string, std::string> mainCache;
std::map<std::string, std::unique_ptr<std::mutex> > mutexCache;
std::mutex gMutex;
.....
public:
std::string get(std::string key);
};
std::string cache::get(std::string key){
std::mutex *m;
gMutex.lock();
if (mutexCache.count(key) == 0){
mutexCache.insert(new std::unique_ptr<std::mutex>);
}
m = mutexCache[key];
gMutex.unlock();
}
I find that I can't create map from string to mutex, because there is no copy constructor in std::mutex and I must use std::unique_ptr; but when I compile this I get:
/home/user/test/cache.cpp:7: error: no matching function for call to 'std::map<std::basic_string<char>, std::unique_ptr<std::mutex> >::insert(std::unique_ptr<std::mutex>*)'
mutexCache.insert(new std::unique_ptr<std::mutex>);
^
How do I solve this problem?
TL;DR: just use operator [] like std::map<std::string, std::mutex> map; map[filename];
Why do you need to use an std::unique_ptr in the first place?
I had the same problem when I had to create an std::map of std::mutex objects. The issue is that std::mutex is neither copyable nor movable, so I needed to construct it "in place".
I couldn't just use emplace because it doesn't work directly for default-constructed values. There is an option to use std::piecewise_construct like that:
map.emplace(std::piecewise_construct, std::make_tuple(key), std::make_tuple());
but it's IMO complicated and less readable.
My solution is much simpler - just use the operator[] - it will create the value using its default constructor and return a reference to it. Or it will just find and return a reference to the already existing item without creating a new one.
std::map<std::string, std::mutex> map;
std::mutex& GetMutexForFile(const std::string& filename)
{
return map[filename]; // constructs it inside the map if doesn't exist
}
Replace mutexCache.insert(new std::unique_ptr<std::mutex>) with:
mutexCache.emplace(key, new std::mutex);
In C++14, you should say:
mutexCache.emplace(key, std::make_unique<std::mutex>());
The overall code is very noisy and inelegant, though. It should probably look like this:
std::string cache::get(std::string key)
{
std::mutex * inner_mutex;
{
std::lock_guard<std::mutex> g_lk(gMutex);
auto it = mutexCache.find(key);
if (it == mutexCache.end())
{
it = mutexCache.emplace(key, std::make_unique<std::mutex>()).first;
}
inner_mutex = it->second.get();
}
{
std::lock_guard<std::mutex> c_lk(*inner_mutex);
return mainCache[key];
}
}
If you have access to c++17, you can use std::map::try_emplace instead of using pointers and it should work just fine for non-copyable and non-movable types!
Your mutexes actually don't protect values. They are released before returning from get, and then other thread can get referrence to the same string second time. Oh, but your cache returns copies of strings, not references. So, there is no point in protecting each string with own mutex.
If you want to protect cache class from concurrent access only gMutex is sufficient. Code should be
class cache
{
private:
std::map<std::string, std::string> mainCache;
std::mutex gMutex;
public:
std::string get(const std::string & key);
void set(const std::string & key, const std::string & value);
};
std::string cache::get(const std::string & key) {
std::lock_guard<std::mutex> g_lk(gMutex);
return mainCache[key];
}
void cache::set(const std::string & key, const std::string & value) {
std::lock_guard<std::mutex> g_lk(gMutex);
mainCache[key] = value;
}
If you want to provide a way for many threads to work concurrently with string instances inside your map and protect them from concurrent access things become more tricky. First, you need to know when thread finished to work with string and release the lock. Otherwise once accessed value becomes locked forever and no other thread can access it.
As possible solution you can use some class like
#include <iostream>
#include <string>
#include <map>
#include <mutex>
#include <memory>
template<class T>
class SharedObject {
private:
T obj;
std::mutex m;
public:
SharedObject() = default;
SharedObject(const T & object): obj(object) {}
SharedObject(T && object): obj(std::move(object)) {}
template<class F>
void access(F && f) {
std::lock_guard<std::mutex> lock(m);
f(obj);
}
};
class ThreadSafeCache
{
private:
std::map<std::string, std::shared_ptr<SharedObject<std::string>>> mainCache;
std::mutex gMutex;
public:
std::shared_ptr<SharedObject<std::string>> & get(const std::string & key) {
std::lock_guard<std::mutex> g_lk(gMutex);
return mainCache[key];
}
void set(const std::string & key, const std::string & value) {
std::shared_ptr<SharedObject<std::string>> obj;
bool alreadyAssigned = false;
{
std::lock_guard<std::mutex> g_lk(gMutex);
auto it = mainCache.find(key);
if (it != mainCache.end()) {
obj = (*it).second;
}
else {
obj = mainCache.emplace(key, std::make_shared<SharedObject<std::string>>(value)).first->second;
alreadyAssigned = true;
}
}
// we can't be sure someone not doing some long transaction with this object,
// so we can't do access under gMutex, because it locks all accesses to all other elements of cache
if (!alreadyAssigned) obj->access([&value] (std::string& s) { s = value; });
}
};
// in some thread
void foo(ThreadSafeCache & c) {
auto & sharedString = c.get("abc");
sharedString->access([&] (std::string& s) {
// code that use string goes here
std::cout << s;
// c.get("abc")->access([](auto & s) { std::cout << s; }); // deadlock
});
}
int main()
{
ThreadSafeCache c;
c.set("abc", "val");
foo(c);
return 0;
}
Of course, real implementation of these classes should have more methods providing more complex semantic, take const-ness into account and so on. But I hope main idea is clear.
EDIT:
Note: shared_ptr to SharedObject should be used because you can't delete mutex while lock is held, so there is no way of safe deleting map entries if value type is SharedObject itself.
Related
I'd like to wrap all usages of a class instance with a mutex. Today I have
std::map<int, std::shared_ptr<MyClass>> classes;
and functions to find and return instances, like:
std::shared_ptr<MyClass> GetClass(int i);
I'd like to ensure that GetClass() can only retrieve an instance if someone else hasn't already retrieved it, with some RAII mechanism. Usage would be like:
void CallingFunction()
{
auto c = GetClass(i); // mutex for class id 'i' is acquired here
// some calls to class
c.SomeFunction();
} // mutex is released here when 'c' goes out of scope
With the mutex acquired by CallingFunction() other threads that wanted to access the same class instance would block on their calls to GetClass().
I've been looking at a few ways of doing it, such as with a wrapper class like:
class ClassContainer
{
public:
std::shared_ptr<Class> c;
std::mutex m;
};
Where I'd modify GetClass() to be:
ClassContainer GetClass(int i);
But I'm having trouble figuring out both where the std::mutex should be kept, I tried initially storing it in the map before moving to using a container class like:
std::map<int, std::pair<std::mutex, std::shared_ptr<MyClass<>>> classes;
but that wasn't working well, now with the ClassContainer how to have ClassContainer lock the std::mutex like std::lock_guard<> when the caller acquires one via a call to GetClass().
I've been looking at a few ways of doing it, such as with a wrapper class like:
Yes this is proper way to do it and you are close, but you cannot keep mutex itself in this class, only locker. And std::unique_lock is a proper type for that as it has necessary move ctor etc. I would make fields private though and create necessary accessors:
class ClassContainer
{
std::shared_ptr<Class> c;
std::uniqe_lock<mutex> lock;
public:
ClassContainer( std::pair<std::mutex,std::shared_ptr<Class>> &p ) :
c( p.second ),
lock( p.first )
{
}
Class * operator->()const { return c.get(); }
Class & operator*() const { return *c; }
};
then usage is simple:
void CallingFunction()
{
auto c = GetClass(i); // mutex for class id 'i' is acquired here
// some calls to class
c->SomeFunction();
// or even
GetClass(i)->SomeFunction();
}
It is Class which should hold the mutex, something like:
class Class
{
public:
// Your methods...
std::mutex& GetMutex() { return m; }
private:
std::mutex m;
};
class ClassContainer
{
public:
ClassContainer(std::shared_ptr<Class> c) :
c(std::move(c)),
l(this->c->GetMutex())
{}
ClassContainer(const ClassContainer&) = delete;
ClassContainer(ClassContainer&&) = delete;
ClassContainer& operator =(const ClassContainer&) = default;
ClassContainer& operator =(ClassContainer&&) = default;
// For transparent pointer like access to Class.
decltype(auto) operator -> () const { return c; }
decltype(auto) operator -> () { return c; }
const Class& operator*() const { return *c; }
Class& operator*() { return *c; }
private:
std::shared_ptr<Class> c;
std::lock_guard<std::mutex> l;
};
ClassContainer GetClass(int i)
{
auto c = std::make_shared<Class>();
return {c}; // syntax which avoids copy/move contructor.
}
and finally usage:
auto&& cc = GetClass(42); // `auto&&` or `const&` pre-C++17, simple auto possible in C++17
cc->ClassMethod();
Simplified demo.
Accidentally, I did something extremely similar recently (only I returned references to objects instead of shared_ptr. The code worked like following:
struct locked_queue {
locked_queue(locked_queue&& ) = default;
mutable std::unique_lock<decltype(queue::mutex)> lock;
const queue::q_impl_t& queue; // std::deque
};
And here is how it would be used:
locked_queue ClassX::get_queue(...) {
return {std::unique_lock<decltype(mutex)>{mutex}, queue_impl};
}
This question has been asked multiple times but mine is a slightly different case. Say I have a std::vector of observers which I notify when a certain event happens:
void SomeClass::doThing() {
// do things ...
// notify observers
for (auto* o : mObservers) {
o->thingHappened();
}
}
What if in the implementation of thingHappened the observer calls a method in SomeClass to remove itself from the observers? What are some of the best ways to handle this?
One possibility is to make a copy of mObservers before the for loop and use it instead, but the extra copy can be wasteful.
Another possibility is to delegate changes to the array to be run after the loop is finished, perhaps setting a lock (just a boolean) before the loop starts and while this lock is set, the methods that mutate the vector delegate themselves to be called after the loop is done when lock is set to false (could be done with a vector of lambdas... quite cumbersome).
If you have control over the signature of thingHappened(), you can change it to return a bool indicating whether it should be removed. Then, you can remove all the values which return true (or false; depends on the semantics you want).
Luckily for us, std::remove_if and std::partition are guaranteed to call the predicate exactly once per object in the range.
void SomeClass::doThing() {
// do things ...
// notify observers
auto newEnd = std::remove_if(mObservers.begin(), mObservers.end(), [](auto *o) {
return o->thingHappened();
});
// assuming mObservers is a vector
mObservers.erase(newEnd, mObservers.end());
}
One way to work around this is to change the data structure. With a std::list the removal of a element only invalidates iterators/references/pointers to that element. Since the rest of the list remains intact all we need to do is get an iterator to the next element before we process the current one. That would look like
for (auto it = the_list.begin(); it != the_list.end();)
{
auto next = std::next(it);
it->call_the_possibly_removing_function();
it = next;
}
What if in the implementation of thingHappened the observer calls a method in SomeClass to remove itself from the observers? What are some of the best ways to handle this?
The following method has worked for me in the past.
Note that your are going to iterate over the observers.
When a client requests to remove an observer to be removed, check whether you are in the middle of iterating over the observers. If you are, set it aside in another vector. If not, remove it from the observers.
After you are done iterating over the observers, remove all the observers that need to be removed.
Note that you are done iterating over the observers.
void SomeClass::removeObserver(Observer* o) {
if ( this->isIterating )
{
observersToRemove.push_back(o);
}
else
{
// Code for real removal of the observer
}
}
void SomeClass::doThing() {
this->isIterating = true;
for (auto* o : mObservers) {
o->thingHappened();
}
for ( auto* o : observersToRemove )
{
// Code for real removal of the observer
}
observersToRemove.clear();
this->isIterating = false;
}
R Sahu's answer provides a flexible technique for solving this problem. The one thing that concerns me about it is the introduction of several variables that you have to manage. However, it's totally possible to wrap the functionality in a utility class.
Here's a sketch of what you could do:
#include <functional>
#include <utility>
#include <vector>
// Note that this is not threadsafe
template <typename Type>
class MutableLock {
bool locked = false;
Type value;
// std::function gives us a more general action,
// but it does come at a cost; you might want to consider using
// other techniques.
std::vector<std::function<void(Type&)>> actions;
public:
class AutoLocker {
MutableLock& lock;
friend class MutableLock<Type>;
explicit AutoLocker(MutableLock& lock)
: lock{ lock }
{
}
public:
~AutoLocker()
{
lock.unlock();
}
};
MutableLock() = default;
// The [[nodiscard]] is a C++17 attribute that
// would help enforce using this function appropriately
[[nodiscard]] AutoLocker lock()
{
locked = true;
return AutoLocker{ *this };
}
void unlock()
{
for (auto const& action : actions) {
action(value);
}
actions.clear();
locked = false;
}
template <typename F>
void action(F&& f)
{
if (!locked) {
f(value);
} else {
actions.emplace_back(std::forward<F>(f));
}
}
// There needs to be some way to expose the value
// not under the lock (so that we can use it when
// we call `lock()`).
//
// Even if your `Type` is not a range, this would
// be fine, as member functions of a template class
// aren't instantiated unless you call them.
//
// However, you may want to expose other ways to
// access the value
auto begin() { return std::begin(value); }
auto end() { return std::end(value); }
auto begin() const { return std::begin(value); }
auto end() const { return std::end(value); }
};
Using it would look something like this:
#include <algorithm>
#include <iostream>
class Observer {
public:
virtual void thingHappened() = 0;
protected:
~Observer() = default;
};
class SomeClass {
MutableLock<std::vector<Observer*>> observers;
public:
void addObserver(Observer* observer)
{
observers.action([observer](auto& observers) {
observers.push_back(observer);
});
}
void remove(Observer const* observer)
{
observers.action([observer](auto& observers) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
});
}
void doSomething()
{
auto lock = observers.lock();
for (auto* observer : observers) {
observer->thingHappened();
}
// when `lock` goes out of scope, we automatically unlock `observers` and
// apply any actions that were built up
}
};
class Observer1 : public Observer {
public:
SomeClass* thing;
void thingHappened() override
{
std::cout << "thing 1\n";
thing->remove(this);
}
};
int main()
{
SomeClass thing;
Observer1 obs;
obs.thing = &thing;
thing.addObserver(&obs);
thing.doSomething();
thing.doSomething();
}
On Coliru
I am wanting to make a class which allows me to lock an object from being modified. It would essentially be a template with a boolean specifying the lock state. Since it is a template, I won't know all the methods that can be called on the internal object, so I need a method to pass calls through...
template<class T>
class const_lock
{
public:
const_lock() : my_lock(false) {}
void set_const_lock(bool state) {my_lock = state;}
// HOW TO IMPLEMENT SOMETHING LIKE THESE????
//
template<typename...Args >
auto operatorANY_OPERATOR (Args...args)
{
if(my_lock != false)
throw std::exception("Objected locked to modification");
return my_value.ANY_OPERATOR(args);
}
template<typename...Args >
auto operatorANY_CONST_OPERATOR (Args...args) const
{
return my_value.ANY_CONST_OPERATOR(args);
}
template<typename...Args >
auto ANY_METHOD(Args...args)
{
if(my_lock != false)
throw std::exception("Objected locked to modification");
return my_value.ANY_METHOD(args);
}
template<typename...Args >
auto ANY_CONST_METHOD(Args...args) const
{
return my_value.ANY_CONST_METHOD(args);
}
private:
bool my_lock;
T my_value;
}
int main()
{
const_lock<std::vector<int>> v;
v.push_back(5);
v.push_back(7);
v.set_const_lock(true);
v.push_back(9); // fails compilation
std::cout << v.at(1) << std::endl; // ok
}
Any help would be appreciated. Thanks!
Edit: changed static assert to throw and exception
What you're trying to do looks rather difficult, but more importantly is over-complicated and unnecessary for what you're trying to do.
Essentially what you're trying to do (correct me if I'm wrong) is create a compile time check of whether you are supposed to able to modify an object at a given time. However, c++ already has a built in way of doing this. Simply declare or pass your object as const or const&, and the compiler will not allow you to modify non-mutable parts of the object. When you want to be able to modify it pass it without const. You can even cast it from const& to regular & when you want to go from code where you can't modify it directly to code where you can, though I don't recommend it.
edit: just saw a comment on the question about no reference arrays. Don't worry about that! The standard library has support for reference wrappers which allow you to essentially store references in arrays or anywhere else.
You can make a generic wrapper class that you can forward the function to using a lambda that captures a reference to the internal member. In this example I am just using an if statement to check if it is "locked" and if it is then we just modify a copy.
template<class T>
class const_lock
{
private:
bool my_lock;
mutable T my_value;
public:
const_lock() : my_lock(false) {}
void set_const_lock() { my_lock = true; }
template<typename F>
auto operator()(F f) const -> decltype(f(my_value))
{
if (my_lock)
{
T temp{my_value}; // make a copy
return f(temp);
}
else
return f(my_value); // modify wrraped value
}
};
int main()
{
const_lock<std::string> cl;
cl([](std::string& s) {
s = "foobar";
});
cl([](std::string& s) {
std::cout << s << std::endl;
});
cl.set_const_lock();
cl([](std::string& s) {
s = "we should still be foobar";
});
cl([](std::string& s) {
std::cout << s;
});
}
This is completely unimplementable. A trivial modification of your source code shows why this won't work.
int main()
{
const_lock<std::vector<int>> v;
v.push_back(5);
v.push_back(7);
if (rand() % 2)
v.set_const_lock(true);
v.push_back(9); // fails compilation
std::cout << v.at(1) << std::endl; // ok
}
You need to completely rethink your approach.
Below is an example illustrating what I would be trying to protect against
class Node
{
public:
Node(int id) : my_id(id) {}
// . . .
int id() {return my_id;}
private:
int my_id;
// . . .
};
class Grid
{
public:
Grid() {}
// . . .
void associate(Node* n) { my_nodes.push_back(n); }
private:
// . . .
std::vector<Node*> my_nodes;
};
Node* find(std::vector<Node>& Nodes, int ID)
{
for(auto i=Nodes.begin(); i!=Nodes.end(); ++i)
{
if (i->id() == ID)
{
return &*i;
}
}
}
main()
{
std::vector<Node> Nodes;
// fill Nodes with data
Grid Array;
Array.associate( find(Nodes,14325) );
Array.associate( find(Nodes,51384) );
Array.associate( find(Nodes,321684) );
// . . .
Nodes.push_back(Node(21616)); // this can invalidate my pointers in Array
}
If I was able to make my Nodes vairable be
const_lock<std::vector<Node>> Nodes;
then call
Nodes.set_const_lock(true);
after populating the data, I wouldn't need to worry about my pointers in Array getting messed up.
I've two sibling classes, A and B, and I want to refactor them so that A is the parent of B, so B can share A's code. But this refactor would mean, for one key function, locking a mutex twice instead of once. What reasons are there not to?
Class A
class A{
std::map<std::string, int> values;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i = values.find(key);
if(i == values.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i->second = v;
}
}
};
Class B
class B{
std::map<std::string, int> values;
std::map<std::string, std::vector<int> > history;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i = values.find(key);
if(i == values.end())return; //Actually report error
auto i2 = history.find(key);
if(i2 == history.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i->second = v;
i2->second.push_back(v);
}
}
};
The alternative to class B that I'd like to write is:
class C:public A{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v){
A::foo(key,v);
auto i2 = history.find(key);
if(i2 == history.end())return; //Actually report error
{
std::lock_guard<std::mutex> lock(mutex);
i2->second.push_back(v);
}
}
};
The access functions also use the mutex, to lock their reads. For the purpose of this question assume foo() gets called a lot more than any other function in these classes. We can also assume all calls to foo() are serialized; the mutex is there for other threads that use the access functions.
Q. Splitting one mutex lock into two locks done in serial cannot add any new deadlock potential?
Q. Is there a bigger code smell from code duplication with class A and class B, or from "hiding" the extra mutex lock in the base class call?
Q. Is the extra overhead of locking twice going to be trivial compared to the other actions I am doing in foo()? I.e. I'm guessing that inserts into maps and vectors takes at least 10 times as long as locking a mutex.
Q. class C now allows a read of values that is out of sync with a read of history (i.e. if another thread grabbed the lock in the middle of C::foo()). If that turns out to be a problem, is going back to "duplicate the code in class A and B" the only design choice?
How about this alternative, which adds a foo_impl function that returns the lock, so it can be re-used in C::foo:
class A
{
std::map<std::string, int> values;
std::mutex mutex;
public:
//init and access functions elided
void foo(std::string key, int v)
{
foo_impl(key, v);
}
protected:
std::unique_lock<std::mutex> foo_impl(std::string key, int v)
{
auto i = values.find(key);
if (i == values.end()) return {}; //Actually report error
std::unique_lock<std::mutex> lock(mutex);
i->second = v;
return lock;
}
};
class C : public A
{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v)
{
auto i2 = history.find(key);
if (i2 == history.end()) return; //Actually report error
if (auto lock = A::foo_impl(key,v))
i2->second.push_back(v);
}
};
This ensures the updates to A::values and C::history are done under a single lock, so A::values cannot get updated again between the two locks in your original C::foo.
I don't understand why you think it's necessary to lock twice, this seem like what you want to do?
class A{
std::map<std::string, int> values;
std::mutex mutex;
protected:
void foo_unlocked(std::string key, int v){
auto i = values.find(key);
if(i != values.end())
i->second = v;
}
};
class C:public A{
std::map<std::string, std::vector<int> > history;
public:
//init and access functions elided
void foo(std::string key, int v){
auto i2 = history.find(key);
if(i2 == history.end())
return; //Actually report error
std::lock_guard<std::mutex> lock(mutex);
i2->second.push_back(v);
foo_unlocked(key, v); // do the operation in A but unlocked...
}
};
I have a stl map which I would like to be synchronized across several threads. Currently I have...
Function A (Modifies map)
void Modify(std::string value)
{
pthread_mutex_lock(&map_mutex);
my_map[value] = value;
pthread_mutex_unlock(&map_mutex);
}
Function B (Reads map)
std::string Read(std::string key)
{
std::string value;
pthread_mutex_lock(&map_mutex);
std::map<std::string, std::string>::iterator it = my_map.find(key);
pthread_mutex_unlock(&map_mutex);
if(it != my_map.end())
{
return it->second;
}
else
{
return "DNE";
}
}
This is synchronized across all threads, due to the mutex. However, I have to lock the mutex in Function B even though it is not modifying the map at all. Is there a way to lock the my_map object itself in function A, and not lock it in function B while keeping thread synchronization. This way, all instances/calls of Function B continue to run freely, so long as Function A is not being run?
Thanks
You don't just want to lock the container, you also want to lock accesses into the container i.e. any iterators or pointers into it. You need to move those accesses into the locked region of the code.
std::string Read(std::string key)
{
std::string value = "DNE";
pthread_mutex_lock(&map_mutex);
std::map<std::string, std::string>::iterator it = my_map.find(key);
if(it != my_map.end())
{
value = it->second;
}
pthread_mutex_unlock(&map_mutex);
return value;
}
There's really no practical way to do this from inside the object itself.
Warning: I have not compiled or tested any of this, but I've done similar things in the past.
Step one would be to control the mutex with class, as such:
class Lock {
public:
Lock(Mutex& mutex) {
pthread_mutex_lock(mutex);
}
~Lock(Mutex& mutex) {
pthread_mutex_unlock(mutex);
}
};
This saves you from all sorts of issues, for instance, if your map throws an exception.
Then your modify becomes:
void Modify(std::string value)
{
Lock(map_mutex);
my_map[value] = value;
}
Create a reference counted lock class:
class RefCntLock {
private:
static int count;
static Lock* lock;
public:
RefCountLock(Mutex& mutex) {
// probably want to check that the mutex matches prior instances.
if( !lock ) {
lock = new Lock(mutex);
count++;
}
}
~RefCountLock() {
--count;
if( count == 0 ) {
delete lock;
lock = NULL;
}
}
};
(Note: it'd be easy to generalize this to deal with multiple mutexes.)
In your read, use the RefCntLock class:
std::string Read(std::string key)
{
{
RefCntLock(&map_mutex);
std::map<std::string, std::string>::iterator it = my_map.find(key);
}
if(it != my_map.end())
{
return it->second;
}
else
{
return "DNE";
}
}
This means that each write gets a lock but all reads share a lock.
In the upcoming C++17 standard, you can use std::shared_mutex and std::shared_lock to allow multiple readers exclusive read access, and std::unique_lock to implement exclusive write access.
std::shared_mutex map_lock;
void Modify(std::string value)
{
auto write_lock = std::unique_lock(map_lock);
my_map[value] = value;
}
std::string Read(std::string key)
{
auto read_lock = std::shared_lock(map_lock);
auto it = my_map.find(key);
return (it != my_map.end()) ? it->second : "DNE";
}