Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.
2464 Discussions

How to lock the whole concurrent_hash_map not warping a portion of code with mutex?

sunqxj
Beginner
867 Views
Originally, I thought TBB provide the range iterator will block all access to the hash table itself. I tried to see whether or not it is the case. I found out, it does not provide this protection. I create two tasks and one for insert number while the other to print all the numbers in the current map. The result gives me either segmentation fault or just not run correctly. Would some one tell me how to lock the whole hash_map table instead of just using mutex around the print map code which just prevents the other task to print it. Also, I tried if I wrap the code with mutex as
task* execute() {
mutex printMutex;
mutex::scoped_lock mylock(printMutex);
    cout << "************** print map ***************** " << endl;
    for (CHM::iterator it = chm.begin(); it != chm.end(); it++) {
        cout << it->first << " -> " << it->second << endl;
    }
    return NULL;
  }

It still gives segmentation fault error. Would some one give me a hint? I am quite new for parallel programming. Thanks.

Here is the code :

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define N 100000

using namespace std;
using namespace tbb;

class hash_func {
    public:
        static size_t hash( const int &n)
        {
            return n;
        }

        static bool equal( const int &s1, const int &s2)
        {
            return s1 == s2;
        }
};

typedef concurrent_hash_map CHM;



class FObject {
    public:
        CHM &chm;
        int *number;

  FObject( CHM &chm_, int * _number ) : chm(chm_), number(_number) {}

        void operator()( const blocked_range r ) const
        {
            for ( int ptr =r.begin(); ptr!=r.end(); ++ptr ) {
                CHM::accessor chm_acc;
                chm.insert( chm_acc, number[ptr]);
                chm_acc->second += 1;
        cout << "insert " << number[ptr]<< endl;
            }
        }
};

class InNum: public task {
public:
  InNum(CHM & _chm, int *_num):chm(_chm),num(_num) {
  }
  CHM &chm;
  int * num ;

  task* execute() {
    parallel_for( blocked_range(0, N), FObject(chm, num) );
    return NULL;
  }
};
class PrintNum: public task {
public:
  PrintNum(CHM & _chm):chm(_chm) {
  }
  CHM &chm;

  task* execute() {
    cout << "************** print map ***************** " << endl;
    for (CHM::iterator it = chm.begin(); it != chm.end(); it++) {
        cout << it->first << " -> " << it->second << endl;
    }
    return NULL;
  }
};
int main()
{
    task_scheduler_init init(-1);

    int words;
    CHM chm;
    for(int i = 0 ; i < N; ++i) {
      words = rand();
    }

    empty_task* parent = new(task::allocate_root()) empty_task;
    parent->set_ref_count(6);
    InNum *inNum = new( parent->allocate_child()) InNum(chm, words);

    parent->spawn(*inNum);
    for(int j = 1; j <= N; ++j) {
      if (j% 20000 == 0 ) {
    PrintNum *printNum = new (parent->allocate_child()) PrintNum(chm);
    parent->spawn(*printNum);
      }

    }

    return 0;
}

0 Kudos
6 Replies
RafSchietekat
Valued Contributor III
867 Views
The iterators in a concurrent_hash_map are only meant for non-cuncurrent access, so use concurrent_unordered_map instead.
0 Kudos
sunqxj
Beginner
867 Views
Thanks Raf for your answer.
I have another question. For concurrent access, in the reference manual, it says concurrent_unordered_map provides concurrent insertion and traversal. Does it also include concurrent modification of the map value? Could you give me a hint on it such as the description of the detail implementation and performance comparing with concurrent_hash_map. Thanks ahead.

0 Kudos
RafSchietekat
Valued Contributor III
867 Views
I guess you'll have to update nonatomically (erase followed by insert). Additional comments welcome.
0 Kudos
Anton_M_Intel
Employee
867 Views
Right, iterator and range in concurrent_hash_map do not work in conjunction with concurrent operations (count, find, insert, erase). concurrent_unordered_map supports concurrent iterators but at the price of removing concurrent erase operation (only serial unsafe_erase) and the burden of item's access synchronization lies on your shoulders.
0 Kudos
sunqxj
Beginner
867 Views
Thanks for both of your reply.

I am trying to figure out what is the best choice for me concurrent_unordered_map or concurren_hash_map.
For my application, my hash key is a structure pointer and my hash value is a link-list. If if the applying hash function on the key results in the same value, then this item will be inserted into the corresponding list. Otherwise, it will insert this pair. I will never delete a pair modification. Sorry for the confusion, I put in my last comment.
For the concurrent traverse, it is not a hard requirement. Therefore, I would like to choose a class which gives me better performance on search, update, insert.
If concurrent_hash_map as what Anton described, I think it would be better for me to swap back to concurrent_hash_map. Is it right? Really appreciate of your suggestions.
Another question, I read the source code of concurrent_hash_map and I am not sure whether or not my understanding is correct. For each node of hash table, there is a spin_rw_mutex asssociated with it, which is the memory cost we pay for concurrency. Also, are there other major memory cost for using concurrent vector?
Also, if the initialization size for the bucket is not provided, will concurrent_hash_map dynamically reorganize the bucket list to get better performance?
0 Kudos
Anton_M_Intel
Employee
867 Views
I don't quite understand what you described above regarding "hash" key, value, and function.. Anyway, if you don't need concurrent erasure and traverse, both containers fit your needs. There is still difference in the interface - .._hash_map provides access syncronization to the elements using spin_rw_locks hidden behind the accessors; .._unordered_map returns iterators which effectivelyare just pure pointers, so you need to synchronizeany updates yourself. Here is main performance difference - if you are able to synchronize updates more effectively than by spin_rw_mutex - use .._unordered_map, otherwise .._hash_map may be somewhat faster. Another difference, which may be important for [soft] realtime apps where preemption+spin_locks can harm, is that .._hash_map is build using fine-grain locking of buckets while .._unordered_map is lock-free (which does not mean it involves less memory synchronization on average).
Both hash tables are designed to grow dynamically. However, it is better for performance and scalability to specify reasonable initial capacity before using them (e.g., rehash() method).
0 Kudos
Reply