C++ thread safety - map reading

2020-04-02 06:38发布

I am working on a program that needs std::map and specifically one like this map<string,map<string,int>> - it is meant to be something like bank change rates - the first string is the original currency and the one in the second map is the desired one and the int is their rate. This whole map will be read only. Do I still need mutexes ? I am a bit confused about the whole thread safety, since this is my first bigger multi-threaded program.

4条回答
狗以群分
2楼-- · 2020-04-02 06:50

If your are completely sure that both the maps are ALWAYS READONLY, Then you never need mutexes.

But you have to be extra careful that no one can update the map by any means during the program execution. Make sure that you are initializing the map at the init stage of program and then never update it for any reason.

If you are confused that, In future you may need to update it in between the program execution, then its better to have macros around the map, which are empty right now. And in future, if you need mutexes around them, just change the macro definition.

PS:: I have used map in answer which can be easily replaced by shared resources. It was for the ease of understanding

查看更多
够拽才男人
3楼-- · 2020-04-02 07:04

If you are talking about the standard std::map and no thread writes to it, no synchronization is required. Concurrent reads without writes are fine.

If however at least one thread performs writes on the map, you will indeed need some sort of protection like a mutex.

Be aware that std::map::operator[] counts as write, so use std::map::at (or std::map::find if the key may not exist in the map) instead. You can make the compiler protect you from accidental writes by only referring to the shared map via const map&.


Was clarified to be the case in the OP. For completeness' sake: Note that other classes may have mutable members. For those, even access through const& may introduce a race. If in doubt, check the documentation or use something else for parallel programming.

查看更多
小情绪 Triste *
4楼-- · 2020-04-02 07:06

The rule of thumb is if you have shared data and at least one thread will be a writer then you need synchronization. If one of the threads is a writer you must have synchronization as you do not want a reader to read an element that is being written to. This can cause issues as the reader might read part of the old value and part of the new value.

In your case since all the threads will only ever being reading data there is nothing they can do that will affect the map so you can have concurrent(unsynchronized) reads.

查看更多
来,给爷笑一个
5楼-- · 2020-04-02 07:14

Wrap a std::map<std::string, std::map<std::string,int>> const in a custom class which has only const member functions [*].

This will make sure that all threads which use an object of the class after its creation will only read from it, which is guaranteed to be safe since C++11.

As documentation says:

All const member functions can be called concurrently by different threads on the same container.

Wrapping containers in your own custom types is good practice anyway. Increased thread safety is just one positive side effect of that good practice. Other positive effects include increased readability of client code, reduction/adaption of container interface to required functionality, ease of adding additional constraints and checks.

Here is a brief example:

class BankChangeRates
{
public:

    BankChangeRates(std::map<std::string, std::map<std::string,int>> const& data) : data(data) {}

    int get(std::string const& key, std::string const& inner_key) const
    {
        auto const find_iter = data.find(key);
        if (find_iter != data.end())
        {
            auto const inner_find_iter = find_iter->second.find(inner_key);
            if (inner_find_iter != find_iter->second.end())
            {
                return inner_find_iter->second;
            }
        }
        // error handling
    }

    int size() const
    {
        return data.size();
    }

private:
    std::map<std::string, std::map<std::string,int>> const data;
};

In any case, the thread-safety problem is then reduced to how to make sure that the constructor does not read from an object to which another thread writes. This is often achieved trivially; for example, the object may be constructed before multi-threading even begins, or it may be initialised with hard-coded initialisation lists. In many other cases, the code which creates the object will generally access only other thread-safe functions and local objects.

The point is that concurrent accesses to your object will always be safe once it has been created.


[*] Of course, the const member functions should keep their promise and not attempt "workarounds" with mutable or const_cast.

查看更多
登录 后发表回答