[英]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));
}
一段时间后,我在两个线程之一中出现段错误或双重释放。 不知何故,我仍然不明白。
我认为这行得通的原因是:
显然,一个或多个我的假设是错误的,或者我没有按照我的想法做。 我认为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 pointer
和reference count
分配。
很可能发生线程2删除共享指针,而在线程1中将指针分配给p
。 存储在shared_ptr
的基础指针不是原子的。
因此,您对std::shared_ptr
的使用不是线程安全的。 只要您不更新或修改基础指针,它都是线程安全的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.