一、条件变量
1.说明
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使“条件成立”(给出条件成立信号)
2.创建和注销
条件变量有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
动态方式调用pthread_cond_init()函数,定义如下:
int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* cond_attr)
# cond_attr的值经常为空,且常被省略
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为linux实现的条件变量没有分配什么资源,因此,注销动作只包括检查是否有等待线程,定义如下:
int pthread_cond_destroy(pthread_cond *cond)
3.等待和激发
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式在给定时刻前没有满足,则返回ETIMEOUT,结束等待;
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* mutex,const struct timespec *abstime )
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。执行pthread_cond_wait()时自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发。
因此上面整个过程可以总结为:
(1)pthread_mutex_lock()上锁
(2)pthread_cond_wait()等待,等待过程分解为:解锁--条件满足---加锁
(3)pthread_mutex_unlock()解锁
激发条件有两种形式:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;
pthread_cond_broadcast()则激活所有等待线程。两者如果没有等待的线程,则什么也不做。
总结为:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况。
二、互斥锁
提供对共享资源的保护访问
1.初始化
在Linux下,线程的互斥量数据类型是pthread_mutex_t.在使用前,要对其进行初始化,初始化有两种方式:
对于静态分配的互斥量,可以把它设置为PTHREAD_MUTEX_INITIALIZER,或者调用pthread_mutex_init.
对于动态分配的互斥量,在申请内存(malloc)之后,通过pthread_mutex_init进行初始化,并在释放内存(free)前需要调用pthread_mutex_destroy
原型:
int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restric attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
2.互斥操作:
对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
3.加锁函数
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
第二个函数是非阻塞调用模式,也就是说如果互斥量没被锁住,trylock函数将把互斥量加锁,并获得对共享资源的访问权限;如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY,表示共享资源处于忙状态。
4.解锁函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.死锁
死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生。
避免死锁的注意事项:
对共享资源操作前一定要获得锁
完成操作后一定要释放锁
尽量短时间的占用锁
如果有多锁,如获得顺序是ABC,那么释放顺序也是ABC
线程错误返回时应该释放它所获得的锁
三、条件变量为什么要和互斥变量一起使用?
互斥锁有一个明显的缺点是它只有两种状态:锁定和非锁定。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足