简体   繁体   English

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

[英]Thread safety in std::map of std::shared_ptr

I know there are a lot of similar questions with answers around, but since I still don't understand this particular case, I decided to pose a question. 我知道周围有很多类似的问题,但由于我仍然不了解这个特殊情况,因此我决定提出一个问题。

What I have is a map of shared_ptrs to a dynamically allocated array (MyVector). 我所拥有的是shared_ptrs到动态分配的数组(MyVector)的映射。 What I want is limited concurrent access without the need to lock. 我想要的是限制并发访问而无需锁定。 I know that the map per se is not thread safe, but I always thought what I'm doing here should be ok, which is: 我知道地图本身不是线程安全的,但是我一直认为我在这里所做的应该没问题,这是:

I fill the map in a single threaded environment like that: 我将地图填充在这样的单线程环境中:

typedef shared_ptr<MyVector<float>> MyVectorPtr;

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

After the initialization, I have one thread that reads from the elements and one that replaces what the shared_ptrs point to. 初始化之后,我有一个线程从元素读取,而一个线程替换了shared_ptrs指向的线程。

Thread 1: 线程1:

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

Thread 2: 线程2:

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

After a while I get either a seg fault or a double free in one of the two threads. 一段时间后,我在两个线程之一中出现段错误或双重释放。 Somehow not really surprisingly, but still I don't really get it. 不知何故,我仍然不明白。

The reasons why I thought this would work, are: 我认为这行得通的原因是:

  1. I don't add or remove any items of the map in the multi-threaded environment, so the iterators should always point to something valid. 我不会在多线程环境中添加或删除地图的任何项目,因此迭代器应始终指向有效的内容。
  2. I thought concurrently changing a single element of the map is fine as long as the operation is atomic. 我认为,只要操作是原子的,同时更改地图的单个元素就可以了。
  3. I thought the operations I do on the shared_ptr (increment ref count, decrement ref count in Thread 1, reset in Thread 2) are atomic. 我以为我对shared_ptr进行的操作(增加引用计数,在线程1中减少引用计数,在线程2中重置)是原子的。 SO Question 所以问题

Obviously, either one ore more of my assumptions are wrong, or I'm not doing what I think I am. 显然,一个或多个我的假设是错误的,或者我没有按照我的想法做。 I think that reset actually is not thread safe, would std::atomic_exchange help? 我认为reset实际上不是线程安全的,std :: atomic_exchange有帮助吗?

Can someone release me? 有人可以释放我吗? Thanks a lot! 非常感谢!

If someone wants to try out, here is the full code example: 如果有人想尝试,这里是完整的代码示例:

#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);
}

I thought concurrently changing a single element of the map is fine as long as the operation is atomic. 我认为,只要操作是原子的,同时更改地图的单个元素就可以了。

Changing the element in a map is not atomic unless you have a atomic type like std::atomic . 除非您具有原子类型(如std::atomic否则更改映射中的元素不是std::atomic

I thought the operations I do on the shared_ptr (increment ref count, decrement ref count in Thread 1, reset in Thread 2) are atomic. 我以为我对shared_ptr进行的操作(增加引用计数,在线程1中减少引用计数,在线程2中重置)是原子的。

That is correct. 那是对的。 Unfortunately you are also changing the underlying pointer. 不幸的是,您还更改了基础指针。 That pointer is not atomic. 该指针不是原子的。 Since it is not atomic you need synchronization. 由于它不是原子的,因此需要同步。

One thing you can do though is use the atomic free functions that are introduced with std::shared_ptr . 但是,您可以做的一件事就是使用std::shared_ptr引入的无原子功能 This will let you avoid having to use a mutex . 这样可以避免使用mutex

TL;DR; TL; DR;

Changing std::map isn't thread safe, while using std::shared_ptr regarding additional references is. 更改std::map并不是线程安全的,而针对其他引用使用std::shared_ptr则是安全的。

You should protect accessing your map regarding read/write operations using an appropriate synchronization mechanism, like eg a std::mutex . 您应该使用适当的同步机制(例如std::mutex来保护有关读写操作的映射。

Also if the state of an instance referenced by the std::shared_ptr should change, it needs to be protected against data races if it's accessed from concurrent threads. 同样,如果std::shared_ptr引用的实例的状态应该更改,则如果要从并发线程访问它,就需要防止数据竞争。


BTW, the MyVector you are showing is a way too naive implementation. 顺便说一句,您显示的MyVector是一种过于幼稚的实现方式。

Lets expand MyVectorPtr p(i->second); 让我们展开MyVectorPtr p(i->second); which is running on thread-1: 在线程1上运行:

The constructor called for this is: 为此调用的构造函数是:

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

Which probably boils down to 2 assignments of the underlying shared pointer and the reference count . 大概可以归结为2个underlying shared pointerreference count分配。

It may very well happen that thread 2 would delete the shared pointer while in thread-1 the pointer is being assigned to p . 很可能发生线程2删除共享指针,而在线程1中将指针分配给p The underlying pointer stored inside shared_ptr is not atomic. 存储在shared_ptr的基础指针不是原子的。

Thus, you usage of std::shared_ptr is not thread safe. 因此,您对std::shared_ptr的使用不是线程安全的。 It is thread safe as long as you do not update or modify the underlying pointer. 只要您不更新或修改基础指针,它都是线程安全的。

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

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