Reputation: 1584
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
Upvotes: 4
Views: 9296
Reputation: 70402
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";
}
Upvotes: 1
Reputation:
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.
Upvotes: 1
Reputation: 308206
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.
Upvotes: 3