我有一个容器(C ++),我需要以两种方式操作,来自不同的线程:1)添加和删除元素,以及2)遍历其成员。显然,在迭代发生时删除元素=灾难。代码看起来像这样:
class A
{
public:
...
void AddItem(const T& item, int index) { /*Put item into my_stuff at index*/ }
void RemoveItem(const T& item) { /*Take item out of m_stuff*/ }
const list<T>& MyStuff() { return my_stuff; } //*Hate* this, but see class C
private:
Mutex mutex; //Goes in the *Item methods, but is largely worthless in MyStuff()
list<T> my_stuff; //Just as well a vector or deque
};
extern A a; //defined in the .cpp file
class B
{
...
void SomeFunction() { ... a.RemoveItem(item); }
};
class C
{
...
void IterateOverStuff()
{
const list<T>& my_stuff(a.MyStuff());
for (list<T>::const_iterator it=my_stuff.begin(); it!=my_stuff.end(); ++it)
{
...
}
}
};
同样,B::SomeFunction()
和C::IterateOverStuff()
被异步调用。我可以使用什么数据结构来确保在迭代期间my_stuff
被“保护”以添加或删除操作?
答案 0 :(得分:15)
听起来需要reader/writer lock。基本上,这个想法是你可能有一个或多个读者 OR 一个作家。你永远不能同时拥有读写锁。
编辑:我认为适合您设计的使用示例包括进行一些小改动。将“iterate”函数添加到拥有列表的类中并使其模板化,以便您可以传递函数/函数来定义要为每个节点执行的操作。像这样的东西(快速和脏的伪代码,但你明白了......):
class A {
public:
...
void AddItem(const T& item, int index) {
rwlock.lock_write();
// add the item
rwlock.unlock_write();
}
void RemoveItem(const T& item) {
rwlock.lock_write();
// remove the item
rwlock.unlock_write();
}
template <class P>
void iterate_list(P pred) {
rwlock.lock_read();
std::for_each(my_stuff.begin(), my_stuff.end(), pred);
rwlock.unlock_read();
}
private:
rwlock_t rwlock;
list<T> my_stuff; //Just as well a vector or deque
};
extern A a; //defined in the .cpp file
class B {
...
void SomeFunction() { ... a.RemoveItem(item); }
};
class C {
...
void read_node(const T &element) { ... }
void IterateOverStuff() {
a.iterate_list(boost::bind(&C::read_node, this));
}
};
另一个选项是使读取器/写入器锁可公开访问,并让调用者负责正确使用锁。但这更容易出错。
答案 1 :(得分:4)
恕我直言,在数据结构类中使用私有互斥体然后编写类方法是错误的,这样无论调用方法的代码是什么,整个事物都是线程安全的。完全和完美地完成这项工作所需的复杂性是最重要的。
更简单的方法是拥有一个公共(或全局)互斥锁,调用代码在需要访问数据时负责锁定。
Here是关于此主题的博客文章。
答案 2 :(得分:3)
当您返回列表时,将其包含在一个类中,该类在其构造函数/析构函数中锁定/解锁互斥锁。
的内容class LockedIterable {
public:
LockedIterable(const list<T> &l, Mutex &mutex) : list_(l), mutex_(mutex)
{lock(mutex);}
LockedIterable(const LockedIterable &other) : list_(other.list_), mutex_(other.mutex_) {
// may be tricky - may be wrap mutex_/list_ in a separate structure and manage it via shared_ptr?
}
~LockedIterable(){unlock(mutex);}
list<T>::iterator begin(){return list_.begin();}
list<T>::iterator end(){return list_.end();}
private:
list<T> &list_;
Mutex &mutex_;
};
class A {
...
LockedIterable MyStuff() { return LockedIterable(my_stuff, mutex); }
};
棘手的部分是编写复制构造函数,以便您的互斥锁不必递归。或者只使用auto_ptr。
哦,读者/作者锁定确实是比mutex更好的解决方案。