【发布时间】:2016-07-12 19:55:23
【问题描述】:
我知道周围有很多类似的问题有答案,但由于我仍然不了解这个特殊情况,所以我决定提出一个问题。
我拥有的是 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));
}
一段时间后,我在两个线程之一中遇到了段错误或双重空闲。不知何故,这并不奇怪,但我还是不太明白。
我认为这可行的原因是:
- 我没有在多线程中添加或删除地图的任何项目 环境,所以迭代器应该总是指向有效的东西。
- 我认为只要操作是原子的,同时更改地图的单个元素就可以了。
- 我认为我在 shared_ptr 上执行的操作(增加线程 1 中的引用计数、减少线程 2 中的引用计数)是原子的。 SO Question
很明显,要么我的一个或多个假设是错误的,要么我没有做我认为的那样。我认为重置实际上不是线程安全的,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);
}
【问题讨论】:
-
MyVector的 3/5/0 规则...(为什么不使用std::vector?) -
if (array != NULL)-- 调用delete[]时不需要检查NULL。 -
shared_ptr::operator=(const shared_ptr &other)如果在另一个线程中分配other,则不是线程安全的。std::atomic<std::shared_ptr<X>>可能不会少锁,所以我想如果你想少锁,你需要自己管理内存。 -
MyVector 是对的,这是一个虚拟类,我用它来表示我在生产代码中遇到的问题。我将编辑该示例,但仍然添加一个复制约束。等对主要问题没有帮助。
-
实际上,我认为编写原子
shared_ptr::operator=(const shared_ptr &other)是不可能的,因为只有在知道对象还活着的情况下才能增加引用计数。阅读other将毫无意义,因为在您实际使用它之前,它可能已被释放。如果没有跨国记忆,您就不能原子地读取一个内存位置并修改另一个。在 x86 上,只能使用 TSX。
标签: c++ multithreading c++11 dictionary shared-ptr