我正在这里阅读这个旧的Boost Thread FAQ,其中有一个指南,用于为具有boost::mutex
不可复制对象作为成员的类实现复制构造和赋值运算符。
我对复制构造函数很好,但我对赋值运算符有一些疑问。 以下说明仍然有效吗?
// old boost thread
const counter & operator=( const counter& other ){
if (this == &other)
return *this;
boost::mutex::scoped_lock lock1(&m_mutex < &other.m_mutex ?
m_mutex : other.m_mutex);
boost::mutex::scoped_lock lock2(&m_mutex > &other.m_mutex ?
m_mutex : other.m_mutex);
m_value = other.m_value;
return *this;
}
不应该更新为:
// new boost thread
const counter& operator=(const counter& other){
if (this == &other)
return *this;
boost::unique_lock<boost::mutex> l1(m_mutex, boost::defer_lock);
boost::unique_lock<boost::mutex> l2(other.m_mutex, boost::defer_lock);
boost::lock(l1,l2);
m_value = other.m_value;
return *this;
}
答案 0 :(得分:5)
首先,我假设问题是在锁定多个任意互斥锁时避免死锁。重要的是始终使用一组互斥锁在整个代码中使用相同的排序约定。如果你可以保证互斥锁A总是在B之前锁定,B总是在C之前锁定,而A总是在C之前锁定,你将避免死锁。
在第一个代码示例中,约定是首先使用较低的内存地址锁定互斥锁。这样可以正常工作,因为地址排序是不变的。第二个版本是避免死锁的official Boost method。文档未指定内部执行的排序。我不建议在源代码中查找它并在代码中的其他地方使用此信息 - 如果库发生更改,它可能会巧妙地破坏。
如果你从头开始(之前你的代码中没有一次持有多个互斥锁),那么使用Boost的方法绝对是可取的 - 你不必担心确切的排序你每次都把它放在一边。如果其余代码使用内存排序,则必须使用它。如果您的代码完全使用其他约定,那么您也需要在此处应用它。在任何情况下,你都不应该在任何可能同时举行的锁定中混合约定,这只是在寻找麻烦。
要回答评论中的问题:
自定义锁定顺序方案在某些情况下可能很有用,特别是如果你需要长时间持有一些锁(A),有些(B)只是暂时,而持有长一。例如,如果您需要在类型A的对象上运行长作业,这会短暂地影响B的许多实例。约定总是首先获取A的锁定,然后 B上的锁定对象:
void doStuff(A& a, std::list<B*> bs) { boost::unique_lock<boost::mutex> la(a.mutex); // lock a throughout for (std::list<B*>::iterator ib = bs.begin(); ib != bs.end(); ++ib) { // lock each B only for one loop iteration boost::unique_lock<boost::mutex> lb(ib->mutex); // work on a and *ib // ... } }
你可能能够在每次循环迭代之间放弃对A的锁定并使用Boost的/ C ++ 0x的锁定顺序,但是根据doStuff()的作用,这可能会使算法更多复杂或混乱。
另一个例子:在运行时环境中,对象不一定停留在同一个内存位置(例如由于复制垃圾收集),依赖内存地址进行排序将不可靠。因此,您可以为每个对象提供唯一的ID,并将锁定顺序基于ID顺序。