前言
在线程的同步机制中我们提到互斥锁能够解决资源的互斥访问,但某些情况,互斥并不能解决,线程经常遇到这种情况:要想继续执行,可能要依赖某种条件,如果条件不满足,它能做的事情就是等待,等到条件满足为止,通常条件的达成,很可能取决于另一个线程,例如系统有全局i和全局j,线程A和线程B都要访问,线程A需要互斥的执行i++;j–操作,线程B需要互斥的在i等于j时执行do_work()函数.情况如下
如果只使用互斥锁,可能导致do_work()函数永远不会执行,分析如下
1.线程A抢到互斥锁,执行后i=4,j=6,然后释放锁
2.线程A和线程B都可能抢到锁,如果线程B抢到锁条件不成立释放锁,如果线程A抢到锁执行后i=5,j=5,释放锁
3.同理,线程A和线程B都可能抢到锁,如果线程B抢到锁条件成立则do_work()后释放锁,如果线程A抢到锁后执行i=6,j=4,此后i=j的情况再也不会发生,就像爱情一样错过了就没有,作为一个稳定的系统我们绝不允许这种存在偶合性情况的发生,也不允许美好爱情的错过,我们就要引入互斥锁结合条件变量来解决问题,下图为改进后的模型,需要强调的是条件变量不能单独使用,必须配合互斥锁一起实现对资源的互斥访问
调用接口介绍
1.初始化,摧毁条件变量
和互斥锁一样,条件变量在使用之前也要进行初始化,互斥锁有静态初始化和动态初始化,条件变量也一样,简单地把PTHREAD_COND_INITIALIZER赋值给pthread_cond_t类型的变量就可以变量的静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态分配条件变量可以或者对条件变量的属性有所定制,都需要用pthread_cond_init进行初始化,成功返回0:
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
//如果attr为NULL,则该条件变量使用默认属性,
函数pthread_cond_destroy()用来销毁条件变量,成功返回0,函数声明如下
int pthread_cond_destroy(pthread_cond_t *cond,const pthread_condattr_t *attr);
注意事项
1.永远不要用一个条件变量对另一个条件变量赋值,即pthread_cond_t cond_b = cond_a不合法
2.使用PTHREAD_COND_INITIALIZER初始化的静态变量不需要被销毁
3.调用pthread_cond_destroy()销毁的条件变量可以调用pthread_cond_init重新进行初始化
4.不要引用已销毁的条件变量
2.条件变量的通知
POSIX提供了如下的两个接口来通知等待的线程,两个函数调用成功都返回0
int pthread_cond_signal(pthread_cond_t *cond);负责唤醒等待在条件变量上的第一个线程
int pthread_cond_broadcast(pthread_cond_t *cond);广播唤醒等待在条件变量上的所有线程
显然,在此函数被调用后,被唤醒的进程又有了互斥量
3.条件变量的等待
pthread_cond_wait用来阻塞等待某个条件变量,函数声明如下
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
第一个参数cond是指向要等待的条件变量的指针
第二个参数mutex是指向与条件变量cond关联的互斥锁指针
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime)
第一个参数cond是指向要等待的条件变量的指针
第二个参数mutex是指向与条件变量cond关联的互斥锁指针
第三个参数abstime是等待过期时的绝对时间
从接口上看条件等待总是和互斥量绑定在一起,这是因为条件不会无缘无故变得满足了,必然会牵扯到共享数据的变化.所以一定要有互斥锁来保护,没有互斥锁,就无法安全地获取和修改共享数据了,所以以上两个函数都包含一个互斥锁,如果某线程因等待条件变量进入等待状态时,将隐含释放申请的互斥锁,就算是需要互斥锁为什么互斥锁和等待变量不能分开呢,比如以下设计
pthread_mutex_lock(&m)
while(condition_is_flase)
{
pthread_mutex_unlock(&m);
cond_wait(&cv);
pthread_mutex_lock(&m);
}
原因在于上面的解锁和等待不是原子操作,在解锁以后,调用cond_wait之前如果已经有其他线程获取到互斥量,并且满足了条件,那么cond_wait将错过这个信号,可能会导致线程永远处于阻塞状态,所以解锁加等待必须原子操作,以确保已经注册到事件的等待队列之前,不会有其他线程可以获得信号,那么先注册等待事件后释放锁不行嘛?要注意的是条件等待是个阻塞型的接口,不单单是注册在等待队列上,线程也会阻塞于此,所以导致互斥量无法释放,造成死锁
例子:
sice@sice:~$ cat test.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int x = 9;
int y = 11;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* fun1(void* arg)
{
printf("fun1() start\n");
pthread_mutex_lock(&lock);
while(x<y)//while效率更高
{
pthread_cond_wait(&cond,&lock);
}
pthread_mutex_unlock(&lock);
sleep(3);
printf("the x is %d,the y is %d\n",x,y);
printf("fun1() end\n");
return 0;
}
void* fun2(void* arg)
{
printf("fun2() start\n");
pthread_mutex_lock(&lock);
x = 100;
y = 2;
//pthread_mutex_unlock(&lock);
if(x>y)
{
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&lock);
printf("fun2() end\n");
return 0;
}
int main(void)
{
pthread_t tid[2];
int flag;
flag = pthread_create(&tid[0],NULL,fun1,NULL);
if(flag)
{
printf("pthread 1 create error\n");
return flag;
}
sleep(3);
flag = pthread_create(&tid[1],NULL,fun2,NULL);
if(flag)
{
printf("pthread 2 create error\n");
return flag;
}
sleep(6);
return 0;
}
结果:
sice@sice:~$ ./test
fun1() start
fun2() start
the x is 100,the y is 2
fun1() end
fun2() end
https://blog.csdn.net/shichao1470/article/details/89856443?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3
该文章值得大家学习