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.
:(