1.3.2 作为数据成员的mutex 不能保护析构
前面的例子说明,作为class 数据成员的MutexLock 只能用于同步本class 的其他数据成员的读和写,它不能保护安全地析构。因为MutexLock 成员的生命期最多与对象一样长,而析构动作可说是发生在对象身故之后(或者身亡之时)。另外,对于基类对象,那么调用到基类析构函数的时候,派生类对象的那部分已经析构了,那么基类对象拥有的MutexLock 不能保护整个析构过程。再说,析构过程本来也不需要保护,因为只有别的线程都访问不到这个对象时,析构才是安全的,否则会有§1.1 谈到的竞态条件发生。
另外如果要同时读写一个class 的两个对象,有潜在的死锁可能。比方说有swap() 这个函数:
- void swap(Counter& a, Counter& b)
- {
- MutexLockGuard aLock(a.mutex_); // potential dead lock
- MutexLockGuard bLock(b.mutex_);
- int64_t value = a.value_;
- a.value_ = b.value_;
- b.value_ = value;
- }
如果线程A 执行swap(a, b); 而同时线程B 执行swap(b, a);,就有可能死锁。operator=() 也是类似的道理。 - Counter& Counter::operator=(const Counter& rhs)
- {
- if (this == &rhs)
- return *this;
- MutexLockGuard myLock(mutex_); // potential dead lock
- MutexLockGuard itsLock(rhs.mutex_);
- value_ = rhs.value_; // 改成value_ = rhs.value() 会死锁
- return *this;
- }
一个函数如果要锁住相同类型的多个对象,为了保证始终按相同的顺序加锁,我们可以比较mutex 对象的地址,始终先加锁地址较小的mutex。