c++thread-safetymutex

How to safely access vector references in C++14


I have a class managing vector data using the singleton pattern.

class DataManager
{
public:
    static DataManager& GetInstance() {
        static DataManager instance;
        return instance;
    }
    const vector<int>& GetData() const{return data;}
    void SetData(vector<int>& data_)
    {
        data.swap(data_);
    }
private:
    vector<int> data;
};

I need to access this data and modify it within a thread. However, if the data is released while accessing it, the program will throw an error.

I made the following attempts:

Pass the lock as a parameter:

class DataManager
{
prublic:
    const std::vector<int>& GetRects(std::unique_lock<std::mutex>& lock) const
    {
        lock = std::unique_lock<std::mutex>(m_mutex_data);
        return data;
    }
...
private:
    mutable std::mutex m_mutex_data;
}

This can work, but it's strange. Is there a better way?


Solution

  • Returning a lock and relying on the user of the class to unlock is very shaky.

    You mentioned that you need to modify the data in a thread-safe way.

    For this you can add a public method to DataManager that will accept a callable that will modify the data, and invoke it under the lock.

    Something like (just a sketch):

    class DataManager
    {
    public:
       template <typename T>
       void TransformDataUnderLock(T && transformer)
       {
           std::lock_guard<std::mutex> lock(m_mutex_data);
           transformer(data);
       }
    
       // ...
    
    private:
       std::vector<int> data;
       mutable std::mutex m_mutex_data;
    };
    

    Notes:

    1. You should also apply similar scope locking in your SetData method (or any other method that accesses data).

    2. This patern might not protect against a mallicious user, but it will protect against accidental wrong usage of the accessed data and the lock (which can be much more common).

    3. This pattern might cause a deadlock due to the fact that the lock is held during invocation of the transformer code which you have no control over. But as long as it is a simple transformation of the data you should be fine.