concurrent_hash_map

concurrent_hash_map#

A concurrent_hash_map<Key, T, HashCompare > is a hash table that permits concurrent accesses. The table is a map from a key to a type T. The traits type HashCompare defines how to hash a key and how to compare two keys.

The following example builds a concurrent_hash_map where the keys are strings and the corresponding data is the number of times each string occurs in the array Data.

#include "oneapi/tbb/concurrent_hash_map.h"
#include "oneapi/tbb/blocked_range.h"
#include "oneapi/tbb/parallel_for.h"
#include <string>


using namespace oneapi::tbb;
using namespace std;


// Structure that defines hashing and comparison operations for user's type.
struct MyHashCompare {
    size_t hash( const string& x ) const {
        size_t h = 0;
        for( const char* s = x.c_str(); *s; ++s )
            h = (h*17)^*s;
        return h;
    }
    //! True if strings are equal
    bool equal( const string& x, const string& y ) const {
        return x==y;
    }
};


// A concurrent hash table that maps strings to ints.
typedef concurrent_hash_map<string,int,MyHashCompare> StringTable;


// Function object for counting occurrences of strings.
struct Tally {
    StringTable& table;
    Tally( StringTable& table_ ) : table(table_) {}
    void operator()( const blocked_range<string*> range ) const {
        for( string* p=range.begin(); p!=range.end(); ++p ) {
            StringTable::accessor a;
            table.insert( a, *p );
            a->second += 1;
        }
    }
};


const size_t N = 1000000;


string Data[N];


void CountOccurrences() {
    // Construct empty table.
    StringTable table;


    // Put occurrences into the table
    parallel_for( blocked_range<string*>( Data, Data+N, 1000 ),
                  Tally(table) );


    // Display the occurrences
    for( StringTable::iterator i=table.begin(); i!=table.end(); ++i )
        printf("%s %d\n",i->first.c_str(),i->second);
}

A concurrent_hash_map acts as a container of elements of type std::pair<const Key,T>. Typically, when accessing a container element, you are interested in either updating it or reading it. The template class concurrent_hash_map supports these two purposes respectively with the classes accessor and const_accessor that act as smart pointers. An accessor represents update (write) access. As long as it points to an element, all other attempts to look up that key in the table block until the accessor is done. A const_accessor is similar, except that is represents read-only access. Multiple const_accessors can point to the same element at the same time. This feature can greatly improve concurrency in situations where elements are frequently read and infrequently updated.

The methods find and insert take an accessor or const_accessor as an argument. The choice tells concurrent_hash_map whether you are asking for update or read-only access. Once the method returns, the access lasts until the accessor or const_accessor is destroyed. Because having access to an element can block other threads, try to shorten the lifetime of the accessor or const_accessor. To do so, declare it in the innermost block possible. To release access even sooner than the end of the block, use method release. The following example is a rework of the loop body that uses release instead of depending upon destruction to end thread lifetime:

StringTable accessor a;
for( string* p=range.begin(); p!=range.end(); ++p ) {
    table.insert( a, *p );
    a->second += 1;
    a.release();
}

The method remove(key) can also operate concurrently. It implicitly requests write access. Therefore before removing the key, it waits on any other extant accesses on key.