我需要以只读方式几乎不断迭代一系列结构,但是对于每1M +读取,其中一个线程可能附加一个项目。我认为在这里使用互斥锁会有点过分,我也会在某处读到r / w lock对读者有其自身的缺点。
我正在考虑在std :: vector上使用reserve(),但这个答案Iterate over STL container using indices safe way to avoid using locks?似乎使这个无效。
关于哪种方式的想法可能最快?最重要的是让读者能够以尽可能少的争用快速有效地进行迭代。写作操作不是时间敏感的。
更新:我的另一个用例是“列表”可能包含指针而不是结构。即,std :: vector。相同的要求适用。
更新2:假设的例子
全球可访问:
typedef std::vector<MyClass*> Vector;
Vector v;
v.reserve(50);
阅读器主题1-10 :(这些运行一直在运行)
.
.
int total = 0;
for (Vector::const_iterator it = v.begin(); it != v.end(); ++it)
{
MyClass* ptr = *it;
total += ptr->getTotal();
}
// do something with total
.
.
作家帖子11-15:
MyClass* ptr = new MyClass();
v.push_back(ptr);
这基本上就是这里发生的事情。线程1-15可以同时运行,尽管通常只有1-2个读取线程和1-2个写入器线程。
答案 0 :(得分:2)
我认为可以在这里工作的是vector
的自己实现,如下所示:
template <typename T> class Vector
{
// constructor will be needed of course
public:
std::shared_ptr<const std::vector<T> > getVector()
{ return mVector; }
void push_back(const T&);
private:
std::shared_ptr<std::vector<T> > mVector;
};
然后,只要读者需要访问特定的Vector
,他们就应该致电getVector()
并保留返回的shared_ptr
,直到读完为止。
但是作者应该始终使用Vector
的{{1}}来添加新值。然后,此push_back
应检查是否为push_back
,如果为真,请分配新 mVector.size() == mVector.capacity()
并将其分配给vector
。类似的东西:
mVector
这里的想法受到RCU(读取 - 复制 - 更新)算法的启发。如果存储空间耗尽,只要至少有一个读取器访问旧存储,新存储不应使旧存储无效。但是,应该分配新存储,并且任何读者在分配之后都应该能够看到它。只要没有人使用旧存储,就应该取消分配旧存储(所有读者都已完成)。
由于大多数硬件架构提供了一些原子递增和递减的方法,我认为template <typename T> Vector<T>::push_back(const T& t)
{
if (mVector->size() == mVector->capacity())
{
// make certain here that new_size > old_size
std::vector<T> vec = new std::vector<T> (mVector->size() * SIZE_MULTIPLIER);
std::copy(mVector->begin(), mVector->end(), vec->begin());
mVector.reset(vec);
}
// put 't' into 'mVector'. 'mVector' is guaranteed not to reallocate now.
}
(因此shared_ptr
)将能够完全无锁地运行。
这种方法的一个缺点是,根据读者持有Vector
的时间长短,您最终可能会得到shared_ptr
的多个副本。
PS:希望我没有在代码中犯下太多令人尴尬的错误: - )
答案 1 :(得分:0)
...在std :: vector上使用reserve()......
如果保证, 你已经声明如果项目不上面的数字,那么你就无法保证。
尽管存在相关问题,但您可以设想使用std::vector
来管理内存,但是需要额外的逻辑层才能解决接受答案中确定的问题。
实际答案是:最快的做法是尽量减少同步量。 的最小同步量取决于您未指定的代码和用法的详细信息。
例如,我使用固定大小的块的链表列出了一个解决方案。这意味着您的常见用例应该与数组遍历一样高效,但是您可以在不重新分配的情况下动态增长。
然而,实施结果对以下问题很敏感: