Deep reading of the container specs is required for the containers to work out what write operations need to be mutexed, and what read operations should be safe even during a write operation.
[Ah you said hashmap]
In a nutshell:
Hashmaps, vectors, and strings are a total pain. It is possible to do stuff that keeps your object pointers good (like pre-allocation), but even single threaded it is hard work to keep references after an insert or delete.
For the traditional maps, sets, lists:
Construction and destruction of the container has to be exclusive, obviously.
If you aren't doing any mods to your map, you can multithread as many reads as you like, of course. As long as you use find() and not operator[] you will be fine.
The behaviour of map and list If you are inserting new objects, then any existing iterators remain valid, but you can't find() or next() (or other iterator arithmetic) during the insert() (assuming the insert() succeeds, so it is often worth doing a find before the insert), and you can't do multiple (successful) inserts in parallel. So you need a 1 writer multi reader locking model: This can be as simple as an atomic find/iterator++ counter and a first/last find vs any insert/delete mutex.
If you are deleting, then , then any iterator pointing to the deleted object becomes invalid, which is also true in single-threaded, but the parallel usage becomes more problematic. Your code needs to have absolute certainty that you don't have any iterators to the deleted element. Depending on your use-case for delete this may not be an issue, or it may be a design killer. I don't see why you would want to delete a resource that you still need/use in another thread, but then I also don't know how your code will know if it is needed. You might need atomic usage counters in each instance, for example, to know which instances are safe to delete at any moment.
If you are doing other mutate operations, you will have to make your own design decisions. These are the main operations, and how I feel they are safe to use.
You can cut it tighter than this with deeper understanding, but these are good performance vs complexity compromises generally.
This applies to most of the containers except vector and string -also hashmap, but for different reasons. But that is because even in single threaded in both cases, insert operations will invalidate existing iterators completely if the storage is moved, and insert and delete operations invalidate any iterators at a higher index. With hashmap the issue is following a rebucket operation pretty much everything you knew before is invalid.
Use of map rather than hashmap in this case can be easier to efficiently lock, but you then have to decide whether the log(n) general performance hit is worth the general low-locking benefit. Alternatively, arrange that your hashmap only contains pointers, so that your code can safely keep pointers to the actual objects rather than iterators or pointers to the contained objects - whicgh are not safe in hashmaps, and blind lock all indexing calls.
std::mapis not a hashmap, but a balanced tree (typically implemented as a red-black tree). If you want a hash map, usestd::unordered_mapinstead.