繁体   English   中英

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

[英]Thread safety in std::map of std::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));
    }

一段时间后,我在两个线程之一中出现段错误或双重释放。 不知何故,我仍然不明白。

我认为这行得通的原因是:

  1. 我不会在多线程环境中添加或删除地图的任何项目,因此迭代器应始终指向有效的内容。
  2. 我认为,只要操作是原子的,同时更改地图的单个元素就可以了。
  3. 我以为我对shared_ptr进行的操作(增加引用计数,在线程1中减少引用计数,在线程2中重置)是原子的。 所以问题

显然,一个或多个我的假设是错误的,或者我没有按照我的想法做。 我认为reset实际上不是线程安全的,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);
}

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

除非您具有原子类型(如std::atomic否则更改映射中的元素不是std::atomic

我以为我对shared_ptr进行的操作(增加引用计数,在线程1中减少引用计数,在线程2中重置)是原子的。

那是对的。 不幸的是,您还更改了基础指针。 该指针不是原子的。 由于它不是原子的,因此需要同步。

但是,您可以做的一件事就是使用std::shared_ptr引入的无原子功能 这样可以避免使用mutex

TL; DR;

更改std::map并不是线程安全的,而针对其他引用使用std::shared_ptr则是安全的。

您应该使用适当的同步机制(例如std::mutex来保护有关读写操作的映射。

同样,如果std::shared_ptr引用的实例的状态应该更改,则如果要从并发线程访问它,就需要防止数据竞争。


顺便说一句,您显示的MyVector是一种过于幼稚的实现方式。

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

为此调用的构造函数是:

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

大概可以归结为2个underlying shared pointerreference count分配。

很可能发生线程2删除共享指针,而在线程1中将指针分配给p 存储在shared_ptr的基础指针不是原子的。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM