最近因為項目要用AsyncDisplayKit,這個框架被稱為下一代視圖的渲染的解決方案,所以在學習基本使用后,決定去研究下源碼,并把學習記錄下來。
首先從ASDisplayNode源文件開始啃,結(jié)果看到很多 ASDN::MutexLocker l(instanceLock) 語句,一臉懵逼,大致揣測是鎖,但是并沒有看到lock和unlock,就只用這條語句,而且很像C++的對象初始化。帶著好奇心深入研究下,才明白大師的編程技巧高深莫測,又get了一個新技能。表示很開心。下面我就分享下:
ASDN::MutexLocker l(instanceLock);
這語句就是C++的對象初始化,對象名稱是l,但是它傳入了一個參數(shù)instanceLock,該參數(shù)定義如下:
ASDN::RecursiveMutex instanceLock;
可以看到他就是一個鎖對象,準確來說是遞歸鎖(允許同一個線程對互斥量多次上鎖(即遞歸上鎖),來獲得對互斥量對象的多層所有權)。
那我們先來看看加鎖怎么和對象初始化糾纏到一起了。這里我們先要看看MutexLocker類的構造函數(shù):
Locker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {
_l.lock ();
}
可以看到構造函數(shù)本質(zhì)是對傳入的參數(shù)調(diào)用了lock函數(shù)。剛開始不懂為什么這樣寫,看到析構函數(shù)后才明白,析構函數(shù)如下:
~Locker () {
_l.unlock ();
}
這樣大致就明白了,由于 線程對lock() 次數(shù)和 unlock() 次數(shù)必須要相同,為了避免忘記加鎖后忘記調(diào)用unlock(),所以作者另辟蹊徑,利用了對象的創(chuàng)建和銷毀,將unlock交由系統(tǒng)自動調(diào)用。大家都知道C++中,對象在銷毀之前會調(diào)用對象的析構函數(shù),所以在加鎖時候定義局部變量l,當函數(shù)執(zhí)行完成時候,局部變量生命周期會結(jié)束,從而調(diào)用析構函數(shù),達到釋放鎖的目的。可以說設計的十分巧妙。
即在某個 Locker 對象的聲明周期內(nèi),它所管理的鎖對象會一直保持上鎖狀態(tài);而 Locker 的生命周期結(jié)束之后,它所管理的鎖對象會被解鎖。這其實就是C++11 lock_guard 的設計思路。
弄懂了,順便將ASDK定義的幾種鎖給弄清楚
首先是Locker類,定義如下:
template<class T>
class Locker
{
T &_l;
public:
Locker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {
_l.lock ();
}
~Locker () {
_l.unlock ();
}
// non-copyable.
Locker(const Locker<T>&) = delete;
Locker &operator=(const Locker<T>&) = delete;
};
這個類主要完成上述的加鎖和自動釋放鎖的功能。
template<class T>
class SharedLocker
{
std::shared_ptr<T> _l;
public:
SharedLocker (std::shared_ptr<T> const& l) ASDISPLAYNODE_NOTHROW : _l (l) {
ASDisplayNodeCAssertTrue(_l != nullptr);
_l->lock ();
}
~SharedLocker () {
_l->unlock ();
}
// non-copyable.
SharedLocker(const SharedLocker<T>&) = delete;
SharedLocker &operator=(const SharedLocker<T>&) = delete;
};
這個鎖是通過C++的共享指針來實現(xiàn)的,這里是通過指針來訪問,而Locker通過對象引用來實現(xiàn),個人感覺是一樣的。
接下來就是重量級的對象,也就是真正實現(xiàn)加鎖和解鎖的對象。
struct Mutex
{
/// Constructs a non-recursive mutex (the default).
Mutex () : Mutex (false) {}
~Mutex () {
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_destroy (&_m));
#if CHECK_LOCKING_SAFETY
_owner = 0;
_count = 0;
#endif
}
Mutex (const Mutex&) = delete; //復制構造函數(shù)和復制賦值操作
Mutex &operator=(const Mutex&) = delete;
void lock () {
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex()));
#if CHECK_LOCKING_SAFETY
mach_port_t thread_id = pthread_mach_thread_np(pthread_self());
if (thread_id != _owner) {
// New owner. Since this mutex can't be acquired by another thread if there is an existing owner, _owner and _count must be 0.
ASDisplayNodeCAssertTrue(0 == _owner);
ASDisplayNodeCAssertTrue(0 == _count);
_owner = thread_id;
} else {
// Existing owner tries to reacquire this (recursive) mutex. _count must already be positive.
ASDisplayNodeCAssertTrue(_count > 0);
}
++_count;
#endif
}
void unlock () {
#if CHECK_LOCKING_SAFETY
mach_port_t thread_id = pthread_mach_thread_np(pthread_self());
// Unlocking a mutex on an unowning thread causes undefined behaviour. Assert and fail early.
ASDisplayNodeCAssertTrue(thread_id == _owner);
// Current thread owns this mutex. _count must be positive.
ASDisplayNodeCAssertTrue(_count > 0);
--_count;
if (0 == _count) {
// Current thread is no longer the owner.
_owner = 0;
}
#endif
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex()));
}
pthread_mutex_t *mutex () { return &_m; }
#if CHECK_LOCKING_SAFETY
bool ownedByCurrentThread() {
return _count > 0 && pthread_mach_thread_np(pthread_self()) == _owner;
}
#endif
protected:
explicit Mutex (bool recursive) {
if (!recursive) {
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, NULL));
} else {
pthread_mutexattr_t attr;
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, &attr));
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
}
#if CHECK_LOCKING_SAFETY
_owner = 0;
_count = 0;
#endif
}
private:
pthread_mutex_t _m;
#if CHECK_LOCKING_SAFETY
mach_port_t _owner;
uint32_t _count;
#endif
};
可以看到本質(zhì)上調(diào)用的就是系統(tǒng)內(nèi)核互斥鎖pthread_mutex_t的方法。這個類構造函數(shù)有一個形參recursive來確定鎖的遞歸性,通過THREAD_MUTEX_RECURSIVE屬性來制定。
如果一個線程對這種類型的互斥鎖重復上鎖,不會引起死鎖,一個線程對這類互斥鎖的多次重復上鎖必須由這個線程來重復相同數(shù)量的解鎖,這樣才能解開這個互斥鎖,別的線程才能得到這個互斥鎖。如果試圖解鎖一個由別的線程鎖定的互斥鎖將會返回一個錯誤代碼。如果一個線程試圖解鎖已經(jīng)被解鎖的互斥鎖也將會返回一個錯誤代碼。這種類型的互斥鎖只能是進程私有的
ASDK項目通常將Mutex作為ASDisplayNode類的一個私有變量,用來控制_view, _layer, _pendingViewState, _subnodes, _supernode, and other properties的線程安全訪問。
另外該文件還封裝了pthread_rwlock_t,原理差不多,這里就不贅述啦。
最后我們看下靜態(tài)互斥鎖StaticMutex。
struct StaticMutex
{
StaticMutex () : _m (PTHREAD_MUTEX_INITIALIZER) {}
// non-copyable.
StaticMutex(const StaticMutex&) = delete;
StaticMutex &operator=(const StaticMutex&) = delete;
void lock () {
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex()));
}
void unlock () {
ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex()));
}
pthread_mutex_t *mutex () { return &_m; }
private:
pthread_mutex_t _m;
};
這種通過靜態(tài)方式初始化互斥鎖,這種方式可以避免啟動時昂貴的對象構造開銷,它也避免在應用程序退出時運行析構函數(shù)(不必要的費用)。
作者推薦以靜態(tài)變量的方式使用StaticMutex作為全局鎖,避免因為StaticMutex的釋放打破其互斥性。