目录
EVUTIL_ASSERT宏
开启调试锁
调试锁结构
调试锁函数
debug_lock_alloc
debug_lock_free
debug_lock_lock
加锁检测
debug_lock_unlock
解锁检测
调试锁下的条件变量函数
总结
以下源码均基于libevent-2.0.21-stable。
在上一章中,分析了线程锁函数以及条件变量函数,其中遗留了一些问题,需要分析调试锁才能得到解决,因此本文就主要分析调试锁。 调试锁是libevent中用户可选的一种模式,它不仅可以调用前面设置的锁函数和条件变量函数,还可以捕获使用锁时的典型错误:重新锁定一个已锁定的非递归锁、解锁一个并未持有的锁。
在正式分析之前,先来说一个libevent中经常用的宏EVUTIL_ASSERT。
该宏定义如下:
#define EVUTIL_ASSERT(cond) \ do { \ if (EVUTIL_UNLIKELY(!(cond))) { \ event_errx(_EVENT_ERR_ABORT, \ "%s:%d: Assertion %s failed in %s", \ __FILE__,__LINE__,#cond,__func__); \ /* In case a user-supplied handler tries to */ \ /* return control to us, log and abort here. */ \ (void)fprintf(stderr, \ "%s:%d: Assertion %s failed in %s", \ __FILE__,__LINE__,#cond,__func__); \ abort(); \ } \ } while (0)在if中调用了一个event_errx函数进行错误提示(可参考libevent日志及错误处理),然后终止进程,因此EVUTIL_ASSERT宏的作用实际上就是进行错误判断。那什么情况认为是发生了错误呢?
if的判断条件是EVUTIL_UNLIKELY(!(cond)),这又是一个宏,该宏的定义如下:
#define EVUTIL_UNLIKELY(p) __builtin_expect(!!(p),0)__builtin_expect其实就是做一个分支预测优化,括号内的第一个参数是一个表达式EXP,第二个参数是一个值N,__builtin_expect的作用就是告诉编译器,EXP==N的概率更大,这样编译器就会进行优化,将更有可能的分支指令放在前面,从而降低跳转指令带来的效率影响。对于这里来说,就相当于告诉编译器,!!(p)这个表达式(第一个参数)的值更可能是0(第二个参数)。这里使用!!(p)的作用是将p转换为布尔值0或1。
因此,EVUTIL_UNLIKELY宏实际上并不会改变参数的值,它只是根据参数来让编译器进行一种优化,如果不管这种优化,完全就可以无视这个宏,if的判断条件就是!(cond)。
因此,EVUTIL_ASSERT(p)实际上是当p为假时报错并终止进程,当p为真时则继续执行。
宏定义中还用到了do{}while(0);这也是一种比较巧妙的用法,可以参考do{}while(0)的好处。
开启调试锁的函数evthread_enable_lock_debuging,用户可直接调用,其定义如下:
void evthread_enable_lock_debuging(void)//开启调试锁后,evthread_lock_callbacks里的锁函数指针以及_evthread_cond_fns全部变为调试锁相关的了。 { struct evthread_lock_callbacks cbs = { EVTHREAD_LOCK_API_VERSION, EVTHREAD_LOCKTYPE_RECURSIVE, debug_lock_alloc, debug_lock_free, debug_lock_lock, debug_lock_unlock }; if (_evthread_lock_debugging_enabled) //如果已经开启了调试锁,就直接返回 return; memcpy(&_original_lock_fns, &_evthread_lock_fns, sizeof(struct evthread_lock_callbacks)); //_original_lock_fns = _evthread_lock_fns 保存原本的锁函数结构 memcpy(&_evthread_lock_fns, &cbs, sizeof(struct evthread_lock_callbacks)); //_evthread_lock_fns = cbs 将调试锁函数赋值给_evthread_lock_fns memcpy(&_original_cond_fns, &_evthread_cond_fns, //_original_cond_fns = _evthread_cond_fns 保存原本的条件变量函数结构 sizeof(struct evthread_condition_callbacks)); _evthread_cond_fns.wait_condition = debug_cond_wait; //将调试锁的条件变量函数赋值给_evthread_lock_fns _evthread_lock_debugging_enabled = 1; /* XXX return value should get checked. */ event_global_setup_locks_(0); }在该函数中,首先定义了一个锁函数结构体cbs,其中已经设定好了一系列的调试锁相关的锁函数。
接着会判断_evthread_lock_debugging_enabled是否为真,_evthread_lock_debugging_enabled的定义如下:
GLOBAL int _evthread_lock_debugging_enabled = 0; //如果调用了evthread_enable_lock_debuging,那么该变量置为1posix下,GLOBAL是一个空宏,而在windows下GLOBAL宏定义为static,因此这里的_evthread_lock_debugging_enabled就是一个全局变量,初始为0。可以看到,在evthread_enable_lock_debuging函数的末尾将_evthread_lock_debugging_enabled置1,而evthread_enable_lock_debuging函数用于开启调试锁。因此,_evthread_lock_debugging_enabled实际上就是一个标志位,标识是否调用了evthread_enable_lock_debuging函数,也就是说,_evthread_lock_debugging_enabled为1时表示开启调试锁,为0时表示没有开启调试锁。
这样第一个if语句就表示如果多次开启调试锁,函数就会直接返回。
接下来是三句memcpy:将_evthread_lock_fns的值赋值给_original_lock_fns;将cbs的值保存到_evthread_lock_fns;将_evthread_cond_fns的值赋值给_original_cond_fns。其中第二句memcpy,将调试锁相关的锁函数保存到了_evthread_lock_fns中,也就是说,在开启调试锁之后,_evthread_lock_fns中存放的是调试锁相关的锁函数(debug_lock_alloc...)。同样的,开启调试锁后,_evthread_cond_fns存放的是调试锁相关的条件变量函数。不过这里需要注意的是,这里并没有将4个条件变量函数全部重新修改,而只是重新为其赋值了wait_condition函数,至于为什么,在后面的调试锁函数章节中会有解释。
到此为止,已经解释了_evthread_lock_fns和_evthread_cond_fns,那么_original_lock_fns和_original_cond_fns又是什么呢?
回看上一章锁函数与条件变量函数设置接口evthread_set_lock_callbacks和evthread_set_condition_callbacks,可以看到这两个函数的开头都有相类似的一句话:
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *cbs) { struct evthread_lock_callbacks *target = _evthread_lock_debugging_enabled ? &_original_lock_fns : &_evthread_lock_fns; ...... } int evthread_set_condition_callbacks(const struct evthread_condition_callbacks *cbs) { struct evthread_condition_callbacks *target = _evthread_lock_debugging_enabled ? &_original_cond_fns : &_evthread_cond_fns; ...... }二者都会先判断_evthread_lock_debugging_enabled变量,当开启了调试锁,那么target就指向_evthread_lock_fns和_evthread_cond_fns,反之target则指向_original_lock_fns和_original_cond_fns。也就是说,如果开启了调试锁,那么二者设置的就是_evthread_lock_fns和_evthread_cond_fns,如果没有开启调试锁,那么二者设置的就是_original_lock_fns和_original_cond_fns,因此,可以理解为:_original_lock_fns和_original_cond_fns中存放的是开启调试锁前的锁函数结构以及条件变量函数结构,这一点可以在后面介绍调试锁函数的时候有更形象的理解。
开启调试锁分析完了,在分析调试锁相关的函数(debug_lock_alloc...)之前,先来看看调试锁结构:
struct debug_lock { //调试锁结构 unsigned locktype; //锁类型 unsigned long held_by; //持有调试锁的线程 /* XXXX if we ever use read-write locks, we will need a separate * lock to protect count. */ int count; //锁被持有的次数 void *lock; //锁变量 };调试锁结构体中有4个成员,分别为锁类型locktype、持有当前锁的线程held_by、锁被持有的次数count以及锁变量lock。其实从这里我们也能大致推断出调试锁是如何进行错误检测的:通过count对加锁次数进行计数,如果count大于1说明多次加锁,如果count小于0说明多次解锁,既然要对加锁此时进行计数,那么可想而知调试锁就必须是一个递归锁。另外还需注意一点,这里有说明提到:如果使用的不是读写锁,那么就应该再单独使用一个锁来保证count变量的线程安全。
那么,既然调试锁必须是递归锁,那么之前evthread_set_lock_callbacks设置的锁的类型去哪了呢?调试锁毕竟只是用来检测的,实际在线程中使用的锁类型仍然是通过evthread_set_lock_callbacks设置的locktype,虽然用了调试锁,但是也不能把之前的锁信息给扔掉,因此这里就需要用locktype来保存之前定义的锁类型。
held_by函数比较容易理解,因为并非是所有锁都只能加锁1次的,如果原来的锁类型本身就是递归锁,那么它是可以加锁多次的,但是递归锁有个前提就是,不管你加锁多少次,都必须是同一个线程。因此就可以用held_by来记录下持有锁的线程,如果是同一个线程,才能对递归锁进行多次加锁。
debug_lock_alloc函数定义如下:
static void * debug_lock_alloc(unsigned locktype) //如果开启了调试锁,就返回一个递归锁,否则返回一个普通锁 设置为递归锁的好处是可以知道锁被持有的次数从而判断状况 { struct debug_lock *result = mm_malloc(sizeof(struct debug_lock)); if (!result) return NULL; if (_original_lock_fns.alloc) { //如果锁分配函数不为空 // locktype|EVTHREAD_LOCKTYPE_RECURSIVE 值只可能为1或者3,这样可以保证分配的锁属性为递归锁 查看evthread_pthread.c中alloc函数 if (!(result->lock = _original_lock_fns.alloc( locktype|EVTHREAD_LOCKTYPE_RECURSIVE))) { //在调试锁的模式下必须要调用之前设置的函数,否则之前设置的锁函数将毫无意义。 // 因此需要用_original_lock_fns将之前的锁函数保存下来 //locktype|EVTHREAD_LOCKTYPE_RECURSIVE的结果保证最低位为1,在alloc中还会将该结果与EVTHREAD_LOCKTYPE_RECURSIVE按位与,由于这里的结果可以保证非0,那么alloc结果也必定非0,分配到的就是一个递归锁 mm_free(result); //如果执行这里说明分配失败,释放result指针返回NULL return NULL; } } else { //如果没有开启调试锁,那么为NULL result->lock = NULL; } result->locktype = locktype; //记录锁的类型 result->count = 0; //刚分配好的锁,其加锁次数为0 result->held_by = 0; //无任何线程持有的锁 return result; }在该函数中,result就用于保存分配的调试锁。函数中会判断_original_lock_fns.alloc是否为空,为什么要对它进行非空判断呢?继续往后看,新定义的result中的lock变量是通过_original_lock_fns.alloc分配得来的,也就是说,在调试锁的分配锁函数中,会去调用_original_lock_fns的alloc函数,而_original_lock_fns前面我们也提到了,保存的是开启调试锁之前的设置好的锁函数,也就是说,调试锁函数中会去调用开启调试锁之前由用户定制的锁函数。
另一个需要注意的地方是,这里alloc的参数为locktype|EVTHREAD_LOCKTYPE_RECURSIVE,其中已经知道EVTHREAD_LOCKTYPE_RECURSIVE宏定义为1,这个位或的结果最低位必然为1。然后以该参数再去调用之前设置号的alloc函数,以posix的alloc函数为例,有如下操作:
static void * evthread_posix_lock_alloc(unsigned locktype) // { ...... if (locktype & EVTHREAD_LOCKTYPE_RECURSIVE) attr = &attr_recursive; ...... }由于之前位或的结果最低位为1,那么它再与EVTHREAD_LOCKTYPE_RECURSIVE进行位与得到的结果也必定非0,因此就必然会将分配的锁属性设置为递归锁属性,因此,alloc的参数设置为locktype|EVTHREAD_LOCKTYPE_RECURSIVE是为了保证调试锁必定是一个递归锁。
debug_lock_free函数定义如下:
static void debug_lock_free(void *lock_, unsigned locktype) { struct debug_lock *lock = lock_; EVUTIL_ASSERT(lock->count == 0); //释放锁时必须确保锁未被持有 即count==0 否则报错 EVUTIL_ASSERT(locktype == lock->locktype); //确保锁的类型是一致的 if (_original_lock_fns.free) { //依然调用之前保存的锁函数 _original_lock_fns.free(lock->lock, lock->locktype|EVTHREAD_LOCKTYPE_RECURSIVE); } lock->lock = NULL; lock->count = -100; mm_free(lock); }首先这里会判断count是否为0,如果count不为0,说明当前锁并非是自由的,因此就直接报错。接下来会判断传入的解锁类型与锁的实际类型是否一致,不过感觉并没有什么明显的效果,目前还没有搞清楚在调用锁释放函数时指定locktype的作用。然后这里仍然是通过_original_lock_fns调用调试前设置的锁函数。
注意到,这里释放锁后,将该调试锁结构的count置为了-100,这样做是为了避免释放锁后又对这个已经释放的锁进行加锁或解锁操作。
debug_lock_lock函数定义如下:
static int debug_lock_lock(unsigned mode, void *lock_) //调试锁加锁 { struct debug_lock *lock = lock_; int res = 0; if (lock->locktype & EVTHREAD_LOCKTYPE_READWRITE) //在目前设定下,如果为1说明为读写锁 EVUTIL_ASSERT(mode & (EVTHREAD_READ|EVTHREAD_WRITE)); //确保mode为EVTHREAD_READ或者EVTHREAD_WRITE else EVUTIL_ASSERT((mode & (EVTHREAD_READ|EVTHREAD_WRITE)) == 0); //确保mode不是以上两种,这里两句ASSERT相当于保证mode和locktype的一致 //debug_lock_lock仅在开启调试锁后调用,_original_lock_fns如果为空,只能是因为没有设置锁函数 if (_original_lock_fns.lock) //执行到这里,说明要么locktype为读写锁,并且mode为READ或者WRITE,要么就是locktype不是读写锁,mode也不是READ和WRITE res = _original_lock_fns.lock(mode, lock->lock); //而目前情况下locktype只支持普通锁和递归锁,因此 if (!res) { //res为0要么是调用lock函数成功,要么就是没有设置锁函数 evthread_debug_lock_mark_locked(mode, lock); } return res; }这里会先通过一个if...else...来确保locktype和锁的模式mode是一致的,以这里的if举例,EVTHREAD_LOCKTYPE_READWRITE宏定义为2,如果locktype与之位与结果为真,说明locktype的二进制第2位为1,就目前的设定来看,也就是说locktype为EVTHREAD_LOCKTYPE_READWRITE,如果位与结果为假,说明locktype就不是读写锁了,如果locktype的类型确定了,那么就要确定相应的mode类型,后面使用EVUTIL_ASSERT就是这个作用,因为这里始终要保证locktype和mode的一致。
后面的很简单,依然是调用之前设定的锁函数_original_lock_fns.lock完成上锁,这里就不多说了。
需要注意的是最后一句话,如果上锁成功(!res),那么就会调用evthread_debug_lock_mark_locked函数,而这个函数,就是实现调试锁一个重要作用的:加锁检测。
加锁检测函数evthread_debug_lock_mark_locked定义如下:
static void evthread_debug_lock_mark_locked(unsigned mode, struct debug_lock *lock) { ++lock->count; //该函数在lock之后调用,因此加锁次数需要加1 if (!(lock->locktype & EVTHREAD_LOCKTYPE_RECURSIVE)) //如果不是递归锁,那么就必须确保当前加锁次数为1 EVUTIL_ASSERT(lock->count == 1); //非递归锁加锁次数如果不等于1,说明加锁异常 //执行到这里说明锁为递归锁,或者为加锁次数为1的非递归锁 if (_evthread_id_fn) { //获取当前线程id, unsigned long me; me = _evthread_id_fn(); if (lock->count > 1) //锁的次数大于1了,说明这个锁肯定是递归锁 EVUTIL_ASSERT(lock->held_by == me); //确保递归锁只能由同一个线程持有,否则报错 lock->held_by = me; //这里赋值用于第一次加锁时将当前线程id赋值给当前调试锁的held_by } }由于调用该函数时,debug_lock_lock已经通过_original_lock_fns.lock完成了上锁,因此首先就对count加1,接下来就会判断锁的类型是否为递归锁,如果不是递归锁,那么就必须保证加锁次数为1,如果加锁次数不为1,说明加锁异常就报错。需要注意的是,这里会检测lock->count大于1和lock->count小于1两种异常情况,前者是指尝试对一个非递归锁进行多次加锁,而后者则是指尝试对一个已释放的锁进行加锁,这一点也说明了为什么前面调试锁free的最后会将count设置为-100。
如果是递归锁的话,那么lock->held_by中必定存放持有该递归锁的线程id,因此就需要将当前线程id与之进行比较,如果id不同,说明这里尝试在不同线程中对同一递归锁进行加锁,就直接报错。
然后最后一点,如果该锁的加锁次数刚好为1,说明这个锁是第一次加锁,不管它是否为递归锁,都需要将当前线程的id赋值给该调试锁的held_by,说明该锁属于当前线程。
debug_lock_unlock函数定义如下:
static int debug_lock_unlock(unsigned mode, void *lock_) { struct debug_lock *lock = lock_; int res = 0; evthread_debug_lock_mark_unlocked(mode, lock); //在解锁之前先进行解锁检测 if (_original_lock_fns.unlock) //解锁 res = _original_lock_fns.unlock(mode, lock->lock); return res; }调试锁的解锁函数也是会通过_original_lock_fns去调用之前保存的锁函数的解锁函数,到这里,应该是对_original_lock_fns的作用有一个确切的认识。解锁就不多说了,唯一需要注意的是,调试锁在真正进行解锁之前,还会调用evthread_debug_lock_mark_unlocked函数进行解锁检测。
解锁检测函数evthread_debug_lock_mark_unlocked定义如下:
static void evthread_debug_lock_mark_unlocked(unsigned mode, struct debug_lock *lock)//检测锁的释放次数,如果释放次数多于加锁次数,就报错 { if (lock->locktype & EVTHREAD_LOCKTYPE_READWRITE) //如果是读写锁 就再次确认mode是否为EVTHREAD_READ或者EVTHREAD_WRITE EVUTIL_ASSERT(mode & (EVTHREAD_READ|EVTHREAD_WRITE)); else //否则确认mode不是EVTHREAD_READ和EVTHREAD_WRITE EVUTIL_ASSERT((mode & (EVTHREAD_READ|EVTHREAD_WRITE)) == 0); if (_evthread_id_fn) { EVUTIL_ASSERT(lock->held_by == _evthread_id_fn()); //同一个锁在某一时刻只能由一个线程持有,如果当前线程不持有该锁就直接解锁,就直接报错 if (lock->count == 1) //如果当前调试锁只被加锁一次,那么此时再解锁,锁就变为自由的了,因此将held_by置0表示无线程持有该锁 lock->held_by = 0; } --lock->count; //持有锁次数减1 EVUTIL_ASSERT(lock->count >= 0); //确保加锁次数非负,如果为负,说明多次释放锁即报错 这种情况就是在同一线程中多次释放锁,count==1后,再次-1就变为了负数 }函数也会先检查mode和locktype是否一致,这一点与debug_lock_lock相同,这里就不多说了。
接下来会确保调试锁的持有线程为当前线程,如果不是,说明当前试图解锁一个当前线程并不持有的锁,那么就直接报错。如果该锁的确由当前线程所持有,并且加锁次数只有一次,那么就将其held_by置0,表示该锁没有任何线程持有了。
以上的情况都是建立在_evthread_id_fn非空的基础上,而如果_evthread_id_fn为空,说明没有定义线程id获取函数,那么就只能不区分线程,而只是对其加锁次数进行最简单的判断了。最简单的判断就是将其count减1,判断加锁次数是否还非负,如果为负数,那就说明当前尝试多次释放同一把锁。
值得注意的是,如果已经设置了线程id获取函数,那么是不可能出现lock->count < 0的情况的,因为当count为1的时候,就会将held_by设置为0,接下来再解锁的话只会在EVUTIL_ASSERT(lock->held_by == _evthread_id_fn());一句中出错。因此,后面的EVUTIL_ASSERT(lock->count >= 0);实际上只针对于未设置线程id获取函数的调试锁进行解锁检测。
前面分析了调试锁下的锁函数,现在再来分析调试锁下的条件变量函数。调试锁下的条件变量函数只有一个wait函数需要定义为debug_wait,这一点不同于锁函数,为了搞清楚这一问题,需要注意以下几个地方。
一个是evthread_set_condition_callbacks函数中,如果在开启调试锁之后进行条件变量函数的设置,那么实际上只是设置了条件变量的alloc、free和signal函数,而独独没有wait函数。
int evthread_set_condition_callbacks(const struct evthread_condition_callbacks *cbs) { ...... if (_evthread_lock_debugging_enabled) { //如果开启了调试锁, _evthread_cond_fns.alloc_condition = cbs->alloc_condition; _evthread_cond_fns.free_condition = cbs->free_condition; _evthread_cond_fns.signal_condition = cbs->signal_condition; } return 0; }另一个需要注意的是,在evthread_enable_lock_debuging函数中,开启调试锁时将原先的锁函数的4个函数全部都修改为了调试锁函数,而对于条件变量函数则只修改了wait函数为调试锁的wait函数。
void evthread_enable_lock_debuging(void)//开启调试锁后,evthread_lock_callbacks里的锁函数指针以及_evthread_cond_fns全部变为调试锁相关的了。 { ...... _evthread_cond_fns.wait_condition = debug_cond_wait; //将调试锁的条件变量函数赋值给_evthread_lock_fns ....... }
换句话说,为什么调试锁的锁函数有4个,而条件变量函数只有1个?
实际上,对于锁函数的alloc、free、lock和unlock来说,alloc用于确保分配的调试锁为递归锁;free在释放锁后会设置count变量为-100,来避免对一个已释放的锁进行加锁;而lock和unlock更不必说了,需要进行加锁和解锁检测的,因此,调试锁的锁函数的4个函数都是与调试锁息息相关的,缺少任何一个都不能进行正确的检测了,因此对于调试锁来说,调试锁的4个锁函数都必须重新设置为debug函数。
而反观条件变量函数,alloc只是用来分配一个条件变量,free用来释放一个条件变量,signal用来通知一个条件变量,条件变量本身也没有什么类型之分,而且和锁也没有任何关系,因此调试锁无需重新设置条件变量函数,直接使用开启调试前设置的条件变量函数即可,后面如果再调用evthread_set_condition_callbacks进行函数设置,那么这3个函数直接设置为传入的参数即可。这也是为什么在evthread_set_condition_callbacks的最后将cbs的alloc、free和signal赋值给调试锁的条件变量函数。
另一方面的wait函数就不一样了,前面一篇文章中有说过,条件变量的wait函数,会先进行解锁然后阻塞,等到唤醒时再重新持有锁,因此这里涉及到了一个解锁和加锁的问题,也就是说,wait函数本身是跟锁有关系的,因此调试锁就必须重新设置新的wait函数,这里设置的是debug_cond_wait函数,该函数定义如下:
static int debug_cond_wait(void *_cond, void *_lock, const struct timeval *tv) //条件变量调用wait的时候会先将锁释放,唤醒时再重新持有锁,因此在调用wait函数前要先检测解锁行为,wait函数返回后检测加锁行为 { int r; struct debug_lock *lock = _lock; EVUTIL_ASSERT(lock); //确保锁是有效的 EVLOCK_ASSERT_LOCKED(_lock); evthread_debug_lock_mark_unlocked(0, lock); //检测锁的解锁行为是否合法 r = _original_cond_fns.wait_condition(_cond, lock->lock, tv); //调用wait evthread_debug_lock_mark_locked(0, lock); //检测锁的加锁行为是否合法 return r; }可见,在debug_cond_wait函数中,由于在wait_condition函数中会先进行解锁,然后进行加锁,因此这里就需要在调用wait_condition之前进行一次解锁检测,在wait_condition函数调用结束后,进行一次加锁检测。
这样,也就解决了为什么调试锁的锁函数需要设置4个,而条件变量函数只设置1个了。
前面已经提到过,内存管理函数、错误日志处理函数,包括这里的线程相关函数都是可以用户进行自定义的,并且这三个相关的定制都应该放在程序运行的最前面,而这三者间定制的顺序应将线程相关函数的定制放在最后,这是因为线程相关的函数如evthread_set_lock_callbacks等都会调用到错误处理函数。而默认的内存管理与错误日志处理二者之间并没有相互的调用关系,因此二者的顺序无关紧要,不过如果需要在内存管理的定制函数中进行错误或日志处理,那么就应该将错误日志处理的定制放在内存管理的前面。
通过以上分析,基本上明白了libevent中的锁和条件变量的流程,总结以下几点:
1.evthread_set_lock_callbacks函数与evthread_set_condition_callbacks函数应当只使用一次,多次使用很容易出现bug;
2.通过evthread_enable_lock_debuging开启调试锁,_evthread_lock_debugging_enabled变量用于标识是否开启了调试锁,并且没有关闭调试锁的函数,如果需要关闭,可以先调用evthread_set_lock_callbacks和evthread_set_condition_callbacks传入参数NULL,然后再重新设置。
3.在开启调试锁之前,_evthread_lock_fns与_evthread_cond_fns分别存放的是当前的锁函数以及条件变量函数;而在开启调试锁之后,_evthread_lock_fns与_evthread_cond_fns则分别存放的是调试锁的锁函数与条件变量函数,而调试锁开启前的锁函数与条件变量函数则存放在_original_lock_fns和_original_cond_fns中。
4.不管是否开启了调试锁,锁的分配、释放、加锁和解锁以及条件变量的分配、释放、等待和通知都是调用_evthread_lock_fns与_evthread_cond_fns中的锁函数和条件变量函数,而在开启了调试锁之后,_evthread_lock_fns与_evthread_cond_fns实际上是通过_original_lock_fns和_original_cond_fns去调用调试锁开启前设置的锁函数与条件变量函数,只不过是调用它们前后加上了一个通过调试锁结构体debug_lock进行加锁检测和解锁检测的环节。
5.锁的分配、释放、加锁和解锁都与锁的检测相关,因此调试锁的4个锁函数都必须重新设置;而条件变量只有等待才与锁的检测相关,因此调试锁只需要重新设置等待函数wait即可。
6.为了安全起见,应该在设置锁函数后再开启调试锁功能。