4

I was looking for a concurrent associative container and I found concurrent_unordered_map from Thead Building Blocks which seems to be fit all of my needs. Even though I read the documentation, I haven't found a single example on how erasure works.

A concurrent_unordered_map supports concurrent insertion and traversal, but not concurrent erasure. The interface has no visible locking. It may hold locks internally, but never while calling user-defined code. It has semantics similar to the C++11 std::unordered_map except as follows:

What does this actually mean? Is it safe to erase from this map as long as I do it just from a single thread? If not, how can I do this?

TheBlackTiger
  • 313
  • 3
  • 9
  • [For the lazy.](http://msdn.microsoft.com/en-us/library/vstudio/hh750089.aspx) – BoBTFish May 25 '13 at 07:42
  • Quote: "The erase methods are prefixed with unsafe_, to indicate that they are not concurrency safe." This is the case for both TBB and ms implementation. – Zamfir Kerlukson May 25 '13 at 07:42
  • 1
    @ZamfirKerlukson I think the asker understands that these methods are not safe, but does not understand how they should be used in a way to make them safe. And looking at the documentation I'm not sure myself. I think you simply cannot do it while any other thread may be doing any operation (safe or otherwise) with the container. i.e. only when no other thread is using it. Which seems stupid, as you have to introduce a lock around the use anyway, right? I suppose these containers are efficient as long as you don't need the unsafe operations during the concurrent phase of your program. – BoBTFish May 25 '13 at 07:52
  • @BoBTFish That was exactly what I was afraid of. Do you know any other concurrent associative containers? – TheBlackTiger May 25 '13 at 08:25

1 Answers1

5

The proposal for standardization of concurrent maps explains why concurrent containers don't have erase. (Search for the section "Why Concurrent Erasure Is Not Supported".)

The containers in the C++ standard library are responsible for deletion of their contents. The contents are "in" the container, much like an element is "in" an array. operator[] returns a reference to the contained object and once the reference is returned the container has no knowledge of how long the requesting thread will "hang on" to the reference. So if another thread comes along asking for the element to be erased, the container has no idea of whether or not the element is safe to delete.

I've been trying to think of some ways you could work around the limitation, but the explanation of the erase problem brings up an even larger issue: it is always the callers responsibility to protect concurrent access to the elements stored in the map.

For example: suppose that you have a concurrent map from int to float and you do this:

thread A                   thread B
the_map[99] = 0.0;
                   ...
the_map[99] = 1.0;         if (the_map[99] == 1.0) ...  // data race!!!

The reason this is a data race is that even though the expression the_map[99] is protected, it returns a reference, accesses to which are not protected. Since the accesses to the reference are not protected then only reads are allowed to the memory the reference points to. (Or you need to have a lock around all accesses to that memory).

Here's the least expensive alternative I can think of (and it is really expensive):

typedef concurrent_unordered_map<MyKey_t, atomic<MyMapped_t*> > MyContainer_t;

Now looking up an item means:

MyMapped_t* x = the_map[a_key].load();

inserting an item is:

the_map[a_key].store(ptr_to_new_value);

erasing an item is:

the_map[a_key].store(0);

and testing whether an item is actually in the map is:

if (the_map[a_key].load() != 0) ...

Finally if you are going to do any kind of conditional erasure (or modification) it has to be something more complicated than:

MyMapped_t* x;
do {
  x = the_map[a_key].load();
} while (condition_for_erasing(x) && !the_map[a_key].compare_exchange_strong(x, 0));

(The above is wrong because it suffers from the ABA problem.)

Even so: this still doesn't protect you from simultaneous modifications to the underlying MyMapped_t and requires you to do all the construction, storage management and destruction of the MyMapped_t's yourself.

:(
Wandering Logic
  • 3,323
  • 1
  • 20
  • 25