std :: map中的线程安全性:std :: shared_ptr

时间:2016-07-12 19:55:24

标签: c++ multithreading c++11 dictionary shared-ptr

我知道答案周围有很多类似的问题,但由于我仍然不理解这个特例,我决定提出一个问题。

我所拥有的是shared_ptrs到动态分配的数组(MyVector)的映射。我想要的是有限的并发访问而无需锁定。我知道地图本身不是线程安全的,但我一直认为我在这里做的应该没问题,这是:

我在这样的单线程环境中填充地图:

typedef shared_ptr<MyVector<float>> MyVectorPtr;

for (int i = 0; i < numElements; i++)
{
    content[i] = MyVectorPtr(new MyVector<float>(numRows));
}

初始化之后,我有一个从元素中读取的线程和一个替换shared_ptrs指向的线程。

主题1:

for(auto i=content.begin();i!=content.end();i++)
{
    MyVectorPtr p(i->second);
    if (p)
    {
        memory_use+=sizeof(int) + sizeof(float) * p->number;
    }
}

主题2:

    for (auto itr=content.begin();content.end()!=itr;++itr)
    {
        itr->second.reset(new MyVector<float>(numRows));
    }

过了一会儿,我在两个线程中的一个中得到了一个seg错误或一个double free。不知怎的,这并不令人惊讶,但我仍然没有得到它。

我认为这会起作用的原因是:

  1. 我不在多线程中添加或删除地图的任何项目 环境,因此迭代器应始终指向有效的东西。
  2. 我认为只要操作是原子的,同时更改地图的单个元素就可以了。
  3. 我认为我对shared_ptr的操作(增量引用计数,线程1中的减量引用计数,线程2中的重置)是原子的。 SO Question
  4. 显然,我的一个或多个假设是错误的,或者我没有按照我的想法行事。我认为重置实际上不是线程安全的,std :: atomic_exchange会有帮助吗?

    有人可以释放我吗?非常感谢!

    如果有人想试试,这是完整的代码示例:

    #include <stdio.h>
    #include <iostream>
    #include <string>
    #include <map>
    #include <unistd.h>
    #include <pthread.h>
    
    
    using namespace std;
    
    template<class T>
    class MyVector
    {
    public:
        MyVector(int length)
        : number(length)
        , array(new T[length])
        {
        }
    
        ~MyVector()
        {
            if (array != NULL)
            {
                delete[] array;
            }
            array = NULL;
        }
    
        int number;
    
    private:
        T* array;
    };
    
    typedef shared_ptr<MyVector<float>> MyVectorPtr;
    
    
    static map<int,MyVectorPtr> content;
    const int numRows = 1000;
    const int numElements = 10;
    
    //pthread_mutex_t write_lock;
    
    double get_cache_size_in_megabyte()
    {
        double memory_use=0;
        //BlockingLockGuard guard(write_lock);
        for(auto i=content.begin();i!=content.end();i++)
        {
            MyVectorPtr p(i->second);
            if (p)
            {
                memory_use+=sizeof(int) + sizeof(float) * p->number;
            }
        }
    
        return memory_use/(1024.0*1024.0);
    
    }
    
    
    void* write_content(void*)
    {
        while(true)
        {
            //BlockingLockGuard guard(write_lock);
            for (auto itr=content.begin();content.end()!=itr;++itr)
            {
                itr->second.reset(new MyVector<float>(numRows));
                cout << "one new written" <<endl;
            }
    
        }
        return NULL;
    }
    
    void* loop_size_checker(void*)
    {
        while (true)
        {
            cout << get_cache_size_in_megabyte() << endl;;
        }
        return NULL;
    }
    
    int main(int argc, const char* argv[])
    {
        for (int i = 0; i < numElements; i++)
        {
            content[i] = MyVectorPtr(new MyVector<float>(numRows));
        }
    
        pthread_attr_t attr;
        pthread_attr_init(&attr) ;
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    
        pthread_t *grid_proc3 = new pthread_t;
        pthread_create(grid_proc3, &attr, &loop_size_checker,NULL);
    
        pthread_t *grid_proc = new pthread_t;
        pthread_create(grid_proc, &attr, &write_content,(void*)NULL);
    
    
        // to keep alive and avoid content being deleted
        sleep(10000);
    }
    

3 个答案:

答案 0 :(得分:2)

  

我认为只要操作是原子的,同时更改地图的单个元素就可以了。

除非你有像std::atomic这样的原子类型,否则更改地图中的元素不是原子的。

  

我认为我对shared_ptr所做的操作(增量引用计数,线程1中的减量引用计数,线程2中的重置)是原子的。

这是正确的。不幸的是,你也在改变底层指针。那个指针不是原子的。由于它不是原子的,你需要同步。

您可以做的一件事是使用std::shared_ptr引入的atomic free functions。这样您就可以避免使用mutex

答案 1 :(得分:0)

<强> TL; DR;

更改std::map不是线程安全的,而使用std::shared_ptr关于其他引用是。

您应该使用适当的同步机制保护访问有关读/写操作的地图,例如: std::mutex

此外,如果std::shared_ptr引用的实例的状态应该更改,则需要保护它免受数据争用的影响(如果从并发线程访问它)。

顺便说一下,你展示的MyVector是一种过于天真的实施方式。

答案 2 :(得分:0)

让我们展开在线程1上运行的MyVectorPtr p(i->second);

要求的构造函数是:

template< class Y > 
shared_ptr( const shared_ptr<Y>& r ) = default;

可能归结为underlying shared pointerreference count的2个作业。

线程2很可能会删除共享指针,而在线程1中指针被分配给p。存储在shared_ptr内的底层指针不是原子。

因此,std::shared_ptr的使用不是线程安全的。只要您不更新或修改基础指针,它就是线程安全的。