互斥锁和条件变量所解决的问题不同,应用的场景不同,就好比有了火车为什么还要有飞机,二者面向的问题不同。
互斥锁主要用来共享资源上
而条件变量主要用来在满足条件时通知线程
互斥锁
线程中有的资源是共享的,那么如果两个线程同时对一个变量做修改就会有一个先后问题,如果访问顺序处理不当,程序的逻辑可能就会受影响,比如下面这个程序,两个线程同时修改number的值,那么number最后的值是多少?可能thread1先执行的话是p = 10010,q = 10510?
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number = 10;
void p(void)
{
int a = 10000;
while(a--) number++;
printf("p中%d\n",number);
}
void q(void)
{
int a = 500;
while(a--) number++;
printf("q中%d\n",number);
}
int main(void)
{
int status;
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,(void*)(&p),NULL);
pthread_create(&tid2,NULL,(void*)(&q),NULL);
pthread_join(tid1,(void*)&status);//等待一个线程的结束
pthread_join(tid2,(void*)&status);
}
其实不然:
之所以会出现这种情况是因为:可能两个线程都从内存中读出number的初始值,thread1在寄存器中完成+1操作后写入内存,后面thread2完成后又写入内存覆盖掉了之前的结果,实际上只要不用互斥锁,多运行几次,各种情况都可能出现。
所以互斥锁就是解决这样的问题,thread1线程加锁后,只有thread1执行完所有操作thread2才能加锁成功,然后独自访问该资源。
**总结:**互斥锁就是保证线程间读写某个数值时的原子性,在执行操作时不会切换到其他线程,实现了原子操作(连续不间断),解决了一些资源竞争问题。
互斥锁死锁
所谓死锁就是线程处于一个既不能加持锁,也不能解除锁,从而无限制等待下去的问题。
典型的两种死锁情形:
(一)线程自己将自己锁住
一般情况下,如果同一个线程先后两次调用lock,在第二次调⽤用时,由于锁已经被占用,该线程会挂起等待占用锁的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此 就永远处于挂起等待状态了,于是就形成了死锁(Deadlock)。
(二)多线程抢占锁资源被困
又如线程A获 得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放 锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都 永远处于挂起状态了,死锁再次形成。
条件变量
既然互斥锁已经解决了一些资源竞争问题,那么为什么还要用到条件变量?
正如开头所言两个应用场景不同,所只有两个状态lock 或者 unlock,如果我们想要达到某种条件时唤醒某个线程呢?
下面摘自AlanGuo的博文
假如我们没有“条件变量”这个概念,如果一个线程要等待某个“自定义的条件”满足而继续执行,而这个条件只能由另一个线程来满足,比如 T1不断给一个全局变量 x +1, T2检测到x 大于100时,将x 置0,如果我们没有条件变量,则只通过互斥锁则可以有如下实现:
/*
* Assume we have global variables:
* int iCount == 0;
* pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
*/
//thread 1:
while(true)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
}
//thread 2:
while(true)
{
pthread_mutex_lock(&mutex);
if(iCount >= 100)
{
iCount = 0;
}
pthread_mutex_unlock(&mutex);
}
这种实现下,就算 lock 空闲,thread2需要不断重复<加锁,判断,解锁>这个流程,会给系统带来不必要的开销。有没有一种办法让 thread2先被 block,等条件满足的时候再唤醒 thread2?这样 thread2 就不用不断进行重复的加解锁操作了?这就要用到条件变量了:
//thread1 :
while(true)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if(iCount >= 100)
{
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
}
//thread2:
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);
}
互斥锁+条件变量
为什么要与pthread_mutex 一起使用呢?
1.这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.
2.保证了只有一个线程基于条件变量阻塞
简而言之就是,在thread 1 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread2 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。给 wait 加锁可以防止同时有另一个线程在 signal。
pthread_cond_wait
实际上,wait会执行一个解锁,然后加锁的操作从而实现同步,这也是wait和sleep的不同之处,sleep()不会解锁,而是单纯的使线程进入休眠状态,下面看一个例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
{
pthread_cleanup_push(pthread_mutex_unlock,&mutex);
while(1)
{
pthread_mutex_lock(&mutex);//互斥锁保证了只有一个线程基于条件变量阻塞
printf("thread1 is running!\n");
pthread_cond_wait(&cond,&mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
printf("thread2 is running\n");
pthread_cond_wait(&cond,&mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
printf("\n\n条件变量学习:\n\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,(void*)thread1,NULL);
pthread_create(&tid2,NULL,(void*)thread2,NULL);
do{
pthread_cond_signal(&cond);
sleep(1);
}while(1);
sleep(50);
pthread_exit(0);
return 0;
}
运行结果:
条件变量学习:
thread1 is running! //线程1加锁,然后基于条件变量阻塞,同时解锁
thread2 is running //线程1阻塞解锁时候,线程2加锁,解除阻塞
thread1 applied the condition //线程2遇到wait阻塞,解锁,线程1得到信号成功加锁
thread1 is running! //...
thread2 applied the condition
thread1 applied the condition
thread2 is running
thread1 is running!
thread2 applied the condition
^C