最近因为项目要用AsyncDisplayKit,这个框架被称为下一代视图的渲染的解决方案,所以在学习基本使用后,决定去研究下源码,并把学习记录下来。
首先从ASDisplayNode源文件开始啃,结果看到很多 ASDN::MutexLocker l(instanceLock) 语句,一脸懵逼,大致揣测是锁,但是并没有看到lock和unlock,就只用这条语句,而且很像C++的对象初始化。带着好奇心深入研究下,才明白大师的编程技巧高深莫测,又get了一个新技能。表示很开心。下面我就分享下:
ASDN::MutexLocker l(instanceLock);
这语句就是C++的对象初始化,对象名称是l,但是它传入了一个参数instanceLock,该参数定义如下:
ASDN::RecursiveMutex instanceLock;
可以看到他就是一个锁对象,准确来说是递归锁(允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权)。
那我们先来看看加锁怎么和对象初始化纠缠到一起了。这里我们先要看看MutexLocker类的构造函数:
Locker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {
_l.lock ();
}
可以看到构造函数本质是对传入的参数调用了lock函数。刚开始不懂为什么这样写,看到析构函数后才明白,析构函数如下:
~Locker () {
_l.unlock ();
}
这样大致就明白了,由于 线程对lock() 次数和 unlock() 次数必须要相同,为了避免忘记加锁后忘记调用unlock(),所以作者另辟蹊径,利用了对象的创建和销毁,将unlock交由系统自动调用。大家都知道C++中,对象在销毁之前会调用对象的析构函数,所以在加锁时候定义局部变量l,当函数执行完成时候,局部变量生命周期会结束,从而调用析构函数,达到释放锁的目的。可以说设计的十分巧妙。
即在某个 Locker 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 Locker 的生命周期结束之后,它所管理的锁对象会被解锁。这其实就是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++的共享指针来实现的,这里是通过指针来访问,而Locker通过对象引用来实现,个人感觉是一样的。
接下来就是重量级的对象,也就是真正实现加锁和解锁的对象。
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; //复制构造函数和复制赋值操作
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
};
可以看到本质上调用的就是系统内核互斥锁pthread_mutex_t的方法。这个类构造函数有一个形参recursive来确定锁的递归性,通过THREAD_MUTEX_RECURSIVE属性来制定。
如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁,一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的
ASDK项目通常将Mutex作为ASDisplayNode类的一个私有变量,用来控制_view, _layer, _pendingViewState, _subnodes, _supernode, and other properties的线程安全访问。
另外该文件还封装了pthread_rwlock_t,原理差不多,这里就不赘述啦。
最后我们看下静态互斥锁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;
};
这种通过静态方式初始化互斥锁,这种方式可以避免启动时昂贵的对象构造开销,它也避免在应用程序退出时运行析构函数(不必要的费用)。
作者推荐以静态变量的方式使用StaticMutex作为全局锁,避免因为StaticMutex的释放打破其互斥性。
网友评论