之前与同学在谈一道smart pointer的面试题,在实现thread safe的时候发现mutex的destroy始终不好解决。问题在于,当你要destroy的mutex之前,必须先unlock这个mutex,而当你unlock这个mutex的同时,你的destroy部分可能不收保护了。与另外一个同学讨论该问题,告之是锁的设计问题。锁不应该和要锁的对象共享一个生存周期。但是,当我们要实现一个对象一个对象的保护的时候,总不能用一个全局锁去管理吧?后来发现,其实,一个对象一个锁,是可以实现完整的保护的。唯一的问题就是destroy这个对象时候。通过增加一个flag方式,我们还是可以保证destroy也能完美的。
下面是具体的代码:
class myObj { public: myObj() { pthread_mutex_init(&_mutex); _isDestroyed = false; } ~myObj() { pthread_mutex_lock(&_mutex); _isDestroyed = true; ... // Do free memory pthread_mutex_unlock(&_mutex); pthread_mutex_destroy(&_mutex); } void doSomeThing() { if (_isDestroyed) return; pthread_mutex_lock(&_mutex); if (_isDestroyed) { pthread_mutex_unlock(&_mutex); return; } ... // Access critical fields of this object pthread_mutex_unlock(&_mutex); } private: bool _isDestroyed; pthread_mutex_t _mutex; }
其中,我们引入了一个flag, _isDestroyed这个boolean变量。在析构函数里面,我们一旦准备锁住mutex,马上把这个flag设置成true,表示这个对象即将被destroy。那么,其他函数在访问所有临界资源的时候,首先需要看看,这个对象是否马上要被destroy了。如果是,则退出,什么也不要做了;如果不是,再获取锁。析构函数只可能被调用一次,所以在析构函数里面,我们不用对_isDestroyed做判断。但是,我们需要让_isDestroyed的变量是在mutex lock之后才能设置。
这样,即使有线程在析构函数的pthread_mutex_unlock(&_mutex)和pthread_mutex_destroy(&_mutex)之间执行了doSomeThing,也不会影响到临界资源的破坏。