互斥量与条件变量
互斥量和条件变量简介
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。在互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥锁加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
条件变量(cond)是在多线程程序中用来实现“等待--》唤醒”逻辑的常用的方法。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”而发出信号。条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前用 pthread_mutex_lock()必须首先锁住互斥量,在更新条件等待队列以前,mutex 保持锁定状态,并在线程挂起进入等待前解锁,在条件满足(pthread_cond_wait()有返回值)后,mutex 将被重新加锁,以与进入 pthread_cond_wait()前的加锁动作一样。这个过程可以表示为:"block-->unlock-->wait() return-->lock"。这 pthread_mutex_lock()和 pthread_cond_wait()是原子操作(所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程))。
1、创建和注销
2、等待和激发
等待条件有两种方式:无条件等待 pthread_cond_wait()和计时等待 pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回 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()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex 保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开 pthread_cond_wait()之前,mutex 将被重新加锁,以与进入 pthread_cond_wait()前的加锁动作对应。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而 pthread_cond_broadcast()则激活所有等待线程。
二、为什么存在条件变量
首先,举个例子:在应用程序中有 4 个进程 thread1,thread2,thread3 和 thread4,有一个 int 类型的全局变量 iCount。iCount 初始化为 0,thread1 和 thread2 的功能是对 iCount 的加 1,thread3 的功能是对 iCount 的值减 1,而 thread4 的功能是当 iCount 的值大于等于 100 时,打印提示信息并重置 iCount=0。
如果使用互斥量,线程代码大概应是下面的样子:
thread1/2:
while (1)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
}
thread4:
while(1)
{
pthead_mutex_lock(&mutex);
if (100 <= iCount)
{
printf("iCount >= 100\r\n");
iCount = 0;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
}
}
在上面代码中由于 thread4 并不知道什么时候 iCount 会大于等于 100,所以就会一直在循环判断,但是每次判断都要加锁、解锁(即使本次并没有修改 iCount)。这就带来了问题一:CPU 浪费严重。所以在代码中添加了 sleep(),这样让每次判断都休眠一定时间。但这又带来的问题二:如果 sleep()的时间比较长,导致 thread4 处理不够及时,等 iCount 到了很大的值时才重置。对于上面的两个问题,可以使用条件变量来解决。
首先看一下使用条件变量后,线程代码大概的样子:
thread1/2:
while(1)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
if (iCount >= 100)
{
pthread_cond_signal(&cond);
}
}
thread4:
while (1)
{
pthread_mutex_lock(&mutex);
while(iCount < 100)
{
pthread_cond_wait(&cond, &mutex);
}
printf("iCount >= 100\r\n");
iCount = 0;
pthread_mutex_unlock(&mutex);
}
从上面的代码可以看出 thread4 中,当 iCount < 100 时,会调用 pthread_cond_wait。而 pthread_cond_wait 在上面应经讲到它会释放 mutex,然后等待条件变为真返回。当返回时会再次锁住 mutex。因为 pthread_cond_wait 会等待,从而不用一直的轮询,减少 CPU 的浪费。在 thread1 和 thread2 中的函数 pthread_cond_signal 会唤醒等待 cond 的线程(即 thread4),这样当 iCount 一到大于等于 100 就会去唤醒 thread4。从而不致出现 iCount 很大了,thread4 才去处理。
需要注意的一点是在 thread4 中使用的 while (iCount < 100),而不是 if (iCount < 100)。这是因为在 pthread_cond_singal()和 pthread_cond_wait()返回之间有时间差,假如在时间差内,thread3 又将 iCount 减到了 100 以下了,那么 thread4 在 pthread_cond_wait()返回之后,显然应该再检查一遍 iCount 的大小,这就是 while 的用意,如果是 if,则会直接往下执行,不会再次判断。
总结:条件变量用于某个线程需要在某种条件成立时才去保护它将要操作的临界区,这种情况从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。在条件满足时,自动退出阻塞,再加锁进行操作。