C++ 具有不可复制boost::mutex的类的赋值运算符
我在这里阅读了这篇老文章,其中有一条指导原则,可以为具有C++ 具有不可复制boost::mutex的类的赋值运算符,c++,multithreading,boost-thread,C++,Multithreading,Boost Thread,我在这里阅读了这篇老文章,其中有一条指导原则,可以为具有boost::mutex不可复制对象作为成员的类实现复制构造和赋值操作符 我对复制构造函数很满意,但对赋值运算符有一些疑问。 下面的说明仍然有效吗 // old boost thread const counter & operator=( const counter& other ){ if (this == &other) return *this; boos
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;
}
//旧的boost线程
常量计数器和运算符=(常量计数器和其他){
如果(此==&其他)
归还*这个;
boost::mutex::作用域锁定锁1(&m\u mutex<&other.m\u mutex?
m_mutex:other.m_mutex);
boost::mutex::作用域锁定锁2(&m\u mutex>&other.m\u mutex)?
m_mutex:other.m_mutex);
m_值=其他m_值;
归还*这个;
}
是否应将其更新为:
// 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;
}
//新的boost线程
常量计数器和运算符=(常量计数器和其他){
如果(此==&其他)
归还*这个;
boost::unique_lock l1(m_互斥体,boost::defer_lock);
boost::unique_lock l2(other.m_mutex,boost::defer_lock);
boost::锁(l1,l2);
m_值=其他m_值;
归还*这个;
}
首先,我假设问题在于锁定多个任意互斥锁时避免死锁。重要的是在使用一组互斥体的代码中始终使用相同的排序约定。如果可以保证互斥锁A总是在B之前锁定,B总是在C之前锁定,A总是在C之前锁定,那么就可以避免死锁
在第一个代码示例中,约定是首先用较低的内存地址锁定互斥锁。这将很好地工作,因为地址顺序是不变的。第二个版本是避免死锁的方法。本文档未指定内部执行的排序。我不建议在源代码中查找它,并在代码的其他地方使用这些信息——如果库发生更改,它可能会微妙地破坏一些东西
如果您是从零开始的(您以前在代码中一次没有持有多个互斥对象),那么使用Boost的方法肯定更可取——您不必担心确切的顺序,只要每次都让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&A,标准::列表bs) { boost::unique_lock la(a.mutex);//锁定a for(std::list::迭代器ib=bs.begin();ib!=bs.end();++ib) { //仅为一次循环迭代锁定每个B boost::unique_lock lb(ib->mutex); //在a和*ib上工作 // ... } } 您可能能够在每次循环迭代之间放弃锁,并使用Boost的/C++0x的锁顺序,但这取决于doStuff()的作用,这可能会使算法更加复杂或混乱
另一个例子:在运行时环境中,对象不一定保持在相同的内存位置(例如,由于复制垃圾收集),依赖内存地址进行排序是不可靠的。因此,您可以给每个对象一个唯一的ID,并根据ID顺序确定锁的顺序。我只想指出,从技术上讲,FAQ中给出的解决方案并不总是有效的。比较这样的指针是未指定的,并且对于这两种比较都很可能产生
true
或false
。正确的方法是对void*
或boost::mutex*
使用std::less
(和kin)的专门化,因为函数比较函子在所有指针专门化上产生总的顺序。(您仍然不能保证有一个特定的结果,但可以保证有一个有效的顺序。)正确。这是针对死锁的。除了boost::lock或内存排序之外,我无法找出其他“排序约定”或方案。你有我可以看的例子吗?旁注:它不仅是boost方法,而且是使用std::lock(l1,l2)的C++0x方法。我同意你的观点。最好不要深入细节。自定义锁排序方案在某些情况下可能很有用,特别是当你需要长时间持有某些锁(A),但某些锁(B)仅在持有长锁时短暂持有。例如,如果您需要在类型A的对象上运行长作业,这会短暂地影响B的许多实例。约定总是先获取A的锁,然后获取B对象上的锁:自定义排序约定的示例对于注释来说太长,所以我把它添加到了答案中。有趣的例子和解释。非常感谢你抽出时间。祝您今天过得愉快。AFG