I am working on an architecture where multiple threads concurrently insert, update, and read elements from a shared map. After researching, I found that Intel TBB's concurrent_hash_map seems suitable for this scenario. However, I encountered an issue in my code where one thread is iterating the map and reading/modifying while other threads are also performing operations like insertion, reading, and modification on the same map.
From my understanding, concurrent_hash_map uses locking at the key level through accessors, making it unsuitable for concurrent iteration while other threads are modifying the map. But it does not support operations like insertion/modification etc while other threads and same thread iterating thru the hashmap.
As an alternative, I looked into concurrent_unordered_map, which claims to handle concurrent iteration while allowing simultaneous writes, modifications, and reads. However, I couldn't find any examples or information on how concurrency is handled in this map.
I wrote some sample code using concurrent_hash_map which will show you my type of data structure used in the code and maybe you people can try to change it into concurrent_unordered_map and handle concurrency so i can learn it better:
#include <iostream>
#include <string>
#include <tbb/concurrent_hash_map.h>
#include <thread>
#include <chrono>
struct ConcurrentHashMapStruct {
struct NestedMapValue {
int field1;
int field2;
};
using InnerHashMap = tbb::concurrent_hash_map<std::string, NestedMapValue>;
using OuterHashMap = tbb::concurrent_hash_map<std::string, InnerHashMap>;
OuterHashMap hashmap;
// get function : returns reference to NestedMapValue (release the const accessor or not ?)
// insert function : how ?
};
void producer(ConcurrentHashMapStruct& concurrentHashMapStruct) {
ConcurrentHashMapStruct::OuterHashMap& hashMap = concurrentHashMapStruct.hashmap;
for (int i = 0; i < 10; ++i) {
std::string outerKey = "outer_key" + std::to_string(i);
std::string innerKey = "inner_key" + std::to_string(i);
int value1 = i;
int value2 = i * 2;
ConcurrentHashMapStruct::OuterHashMap::accessor outerAccessor;
if (hashMap.insert(outerAccessor, outerKey)) {
ConcurrentHashMapStruct::InnerHashMap& nestedHashMap = outerAccessor->second;
ConcurrentHashMapStruct::InnerHashMap::accessor innerAccessor;
if (nestedHashMap.insert(innerAccessor, innerKey)) {
ConcurrentHashMapStruct::NestedMapValue& nestedMapValue = innerAccessor->second;
nestedMapValue.field1 = value1;
nestedMapValue.field2 = value2;
std::cout << "Producer: Inserted value (" << value1 << ", " << value2 << ") at [" << outerKey << "][" << innerKey << "]" << std::endl;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer(const ConcurrentHashMapStruct& concurrentHashMapStruct) {
const ConcurrentHashMapStruct::OuterHashMap& hashMap = concurrentHashMapStruct.hashmap;
ConcurrentHashMapStruct::OuterHashMap::const_iterator outerIt;
for (outerIt = hashMap.begin(); outerIt != hashMap.end(); ++outerIt) {
const std::string& outerKey = outerIt->first;
const ConcurrentHashMapStruct::InnerHashMap& nestedHashMap = outerIt->second;
ConcurrentHashMapStruct::InnerHashMap::const_iterator innerIt;
for (innerIt = nestedHashMap.begin(); innerIt != nestedHashMap.end(); ++innerIt) {
const std::string& innerKey = innerIt->first;
const ConcurrentHashMapStruct::NestedMapValue& nestedMapValue = innerIt->second;
std::cout << "Consumer: Retrieved value (" << nestedMapValue.field1 << ", " << nestedMapValue.field2 << ") from [" << outerKey << "][" << innerKey << "]" << std::endl;
}
}
}
int main() {
ConcurrentHashMapStruct concurrentHashMapStruct;
// Create producer and consumer threads
std::thread producerThread(producer, std::ref(concurrentHashMapStruct));
producerThread.join();
// modify one field here
ConcurrentHashMapStruct::OuterHashMap::accessor outerAccessor;
ConcurrentHashMapStruct::InnerHashMap::accessor innerAccessor;
concurrentHashMapStruct.hashmap.insert(outerAccessor,"outer_key0");
ConcurrentHashMapStruct::InnerHashMap& nestedHashMap = outerAccessor->second;
nestedHashMap.insert(innerAccessor,"inner_key0");
ConcurrentHashMapStruct::NestedMapValue& nestedMapValue = innerAccessor->second;
nestedMapValue.field1 = 654;
outerAccessor.release();
innerAccessor.release();
std::thread consumerThread(consumer, std::cref(concurrentHashMapStruct));
consumerThread.join();
return 0;
}
Here's the compiler explorer link : https://godbolt.org/z/fh119v8fb
My questions are:
- Does concurrent_unordered_map provide key-level locking or another mechanism for concurrency control during modification operations?
- Can I safely iterate over a concurrent_unordered_map while other threads are modifying it concurrently?
- Are there any code examples or resources available that demonstrate the proper usage of concurrent_unordered_map in scenarios with concurrent iteration and modification? I appreciate any insights, code examples, or suggestions regarding how to handle concurrent iteration and modification in a shared map using Intel TBB. Thank you!