在多线程环境中使用和共享的对象中的列表上返回迭代器是否是一个好主意?
class RequestList
{
public:
RequestList::RequestList();
RequestList::~RequestList();
std::list<boost::shared_ptr<Request> >::iterator GetIterator();
int ListSize();
void AddItem(boost::shared_ptr<Request> request);
void RemoveItem(boost::shared_ptr<Request> request);
std::list<boost::shared_ptr<Request> > GetRequestsList();
boost::shared_ptr<Request> GetRequest();
private:
std::list<boost::shared_ptr<Request> > requests;
std::list<boost::shared_ptr<Request> >::iterator iter; //Iterator
boost::mutex listmtx;
};
std::list<boost::shared_ptr<Request> >::iterator RequestList::GetIterator()
{
return this->iter;
}
使用:
RequestList* requests;
在某个线程中(可能在其他线程中再次使用)
std::list<boost::shared_ptr<Request> >::iterator iter = requests->GetIterator();
或者每次只为该列表创建一个迭代器并在每个线程中本地使用它会更聪明吗?
答案 0 :(得分:3)
通常不是跨线程共享迭代器通常不是一个好主意。有几种方法可以确保您没有遇到麻烦。
首先,迭代器是一个轻量级对象,构造速度快,占用的内存很少。所以你不应该担心任何性能问题。只需在需要时创建一个实例。
那说你必须确保在迭代时不改变你的列表。我看到你班上有boost::mutex
。锁定非常适合确保在迭代时不会出现任何问题。
处理这些情况的另一种同样有效的方法是简单地复制内部列表并迭代它。如果您需要不断更新列表并且不希望其他线程等待,这是一个很好的解决方案。当然它占用了更多的内存,但由于你存储了智能指针,它几乎不会有任何东西。
答案 1 :(得分:2)
取决于列表的使用方式,但从您显示的内容看起来是错误的。如果删除了引用的元素,则迭代器将变为无效:在这种情况下,元素是列表中的shared_ptr
对象。
一旦释放互斥锁,我猜其他一些线程可能会出现并删除该元素。您没有显示执行此操作的代码,但如果可能发生,则迭代器不应该“转义”互斥锁。
我认为这是一个“自同步”容器,因为互斥锁是私有的,并且API中没有任何东西可以锁定它。这些事情的根本困难在于,从外部对它们执行任何类型的迭代都不是线程安全的。提供支持以下内容的线程安全队列非常简单:
除此之外,提供有用的基本操作更加困难,因为几乎任何以任何有趣的方式操作列表的东西都需要完全在锁定下完成。
从外观上看,您可以使用GetRequestsList
复制列表,然后遍历副本。不确定它对你有什么好处,因为副本很快就会过时。
答案 2 :(得分:1)
通过主线列表本身未锁定的多个线程中的迭代器访问列表是危险的。
当你在不同的线程中使用迭代器进行操作时,无法保证列表的状态(例如,一个线程可以愉快地遍历并擦除所有项目,另一个线程将是什么,谁也在迭代,请参阅?)
如果您要在多个线程中处理列表,请先锁定整个列表,然后执行您需要的操作。复制列表是一个选项,但不是最佳选择(取决于列表的大小以及更新的速度)。如果锁定成为一个瓶颈,重新考虑你的架构(例如每个线程列表?)
答案 3 :(得分:0)
每个调用GetIterator
函数的线程都会在列表中获得自己的存储迭代器副本。
由于std::list<>::iterator
是双向迭代器,因此您创建的任何副本都完全独立于源。如果其中一个更改,则 将反映在任何其他迭代器中。
至于在多线程环境中使用迭代器,这与单线程环境没有什么不同。只要它引用的元素是容器的一部分,迭代器仍然有效。访问/修改容器或其元素时,您只需要处理正确的同步。
答案 4 :(得分:0)
如果您的某个主题修改了该列表,则可能会遇到麻烦。
但是,当然,您可以通过在修改期间设置锁定和锁定和锁定来解决这个问题。但是,由于互斥体是任何高性能程序的scurge,也许你可以复制列表(或引用)并保持原始列表互斥和无锁?那将是最好的方式。
如果你有互斥锁,你只需按照修改列表的问题进行操作,同时按照通常的方式操作迭代器 - 即添加元素应该没问题,删除应该“小心”但是在list
上进行此操作可能不像vector
那样爆炸: - )
答案 5 :(得分:0)
我会重新考虑这个设计,并会使用基于任务的方法。这样您就不需要任何互斥锁。
例如,使用Intel TBB,它在内部初始化任务池。因此,您可以轻松实现单作者/多读者概念。
首先将所有请求添加到您的请求容器(一个简单的std :: vector可能更适合缓存局部性和性能),然后对您的请求向量执行parallel_for()但不要删除并行中的请求 - for()仿函数!
处理完成后,您可以实际清除请求向量,而无需锁定互斥锁。就是这样!
我希望我能帮上忙。