上篇介绍了libvirt中的job机制是如何使用的,在介绍job机制的原理之前,需要介绍条件变量是如何使用的,因为在libvirt中使用的job机制,是基于条件变量的。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”;另一个线程使得“条件成立”(即发出条件变量)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
条件变量创建
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为信号量定义了属性,但是在LinuxThreads中未实现,因此cond_attr值通常为NULL,且被忽略。
条件变量注销
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有thread等待该条件变量的时候才能注销这个条件变量,否则返回EBUSY。因为Linux在实现条件变量时并未分配什么资源,因此注销动作仅包含检查是否有等待线程。
注销函数定义如下:
int pthread_cond_destroy(pthread_cond_t *cond)
条件变量等待
条件变量的等待方法包括两种:
无条件等待pthread_cond_wait()和计时等待pthread_cond_timewait(),其中计时等待方式如果在既定时刻前未满足条件,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格里尼治时间(1970.1.1.0:0:0)
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait())的竞争条件(Race Condition)。
mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本thread加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁(即将con放入队列之后,再解锁)。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
/************pthread_cond_wait()的使用方法**********/
pthread_mutex_lock(&mutex)
pthread_cond_wait(&cond &mutex);
pthread_mutex_unlock(&cond);
/*****************************************************/
这里需要说明的是,在libvirt中qemuDomainObjBeginJobInternal函数中调用的是virCondWaitUntil(&priv->job.cond, &obj->parent.lock, then) ,其中传入的互斥量是obj->parent.lock,这个互斥量是在进入API的时候,就已经被获取了,相当于这里的pthread_mutex_lock。
激发条件
有两种形式,pthread_cond_signal()激活有个等待条件的线程,存在多个等待thread时按入队列顺序激活其中一个;pthread_cond_broadcast()则激活所有等待thread。
pthread_mutex_lock(&mutex);
ready = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
在发送条件变量信号的时候,也得加锁。但是在libvirt中看不到这个加锁和解锁的过程,原因是加锁是在进入API时完成的,解锁是在退出API时完成的,而发出信号是在这期间完成的。
为什么要加互斥锁,其目的是什么
首先需要明白为什么要加互斥锁,不加互斥锁会导致什么问题,互斥锁保护的是什么变量。
1) 首先需要搞明白条件变量等待时传入的互斥锁,这个锁是保护的什么变量。这个锁不是用来保护条件变量的内部状态的,而是用来保护外部的共享变量的。退一步说,即使条件变量的内部状态需要锁定,完全可以在内部实现中维护一个锁,而没必要从外部传进来。
2) 不加锁会导致什么问题
首先要明白,这个锁传进去之后,还会进行解锁的。但是什么时候解锁呢?就是把条件变量等待这个事件放入到队列之后,线程挂起之前的这一时刻会解锁。
以代码讲解为例:
Thread A:
1: pthread_mutex_lock(&mutex);
2: while (!ready) {
3: pthread_cond_wait(&cond, &mutex);
4: }
5: pthread_mutex_unlock(&mutex);
Thread B:
1: ready = true;
2: pthread_cond_signal(&cond);
由于Thread B中没有加锁,导致Thread A和Thread B在调用的时候,有可能出现如下的调用顺序:
A1->A2->B1->B2->A3
那么可能导致的情况是什么呢?就是在ThreadA中本来是根据全局共享变量来判断是否等待信号量的,结果在等待之前发生线程切换导致先发送信号,之后,ThreadA再进行等待,此时就变成了无效等待,出现信号丢失的情况。
因此,在调进行条件变量等待的时候,必须传入互斥量,这个互斥量是保护判断是否需要等待的全局共享变量的。在发送信号的时候,也需要对全局变量进行保护。