Concurrent Iteration and Modification in TBB's concurrent_unordered_map

251 views Asked by At

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!
0

There are 0 answers