Linux多线程C++版(八) 线程同步方式-----条件变量

16 篇文章 2 订阅
9 篇文章 3 订阅

1.条件变量基本概念
  • 互斥锁的缺点是它只有两种状态:锁定和非锁定
  • 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足
  • 条件变量内部是一个等待队列,放置等待线程,线程在条件变量上等待和通知,互斥锁用来保护等待队列(对等待队列上锁),条件变量通常和互斥锁一起使用。
  • 条件变量允许线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生变化。一旦其他的某个线程改变了条件,可唤醒一个或多个阻塞的线程
  • 具体的判断条件还需要用户给出
  • 条件变量数据类型 pthread_cond_t
2.条件变量创建和销毁
//条件变量的定义
pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *restrict cond,pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *restrict cond);
返回:成功返回0 出错返回错误编号
  • 参数
    • cond:条件变量
    • attr:条件变量属性
3.条件变量等待操作
//线程等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//线程等待一段时间,如果到时间就返回
int pthread_cond_timewait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict timeout);
返回:成功返回0 出错返回错误编号
struct timespec{
    time_t tv_sec;// seconds
    long tv_nsec //nanoseconds
}
  • 参数
    • cond:条件变量
    • mutex:互斥锁
  • 互斥锁mutex是对条件变量cond的保护
  • 线程由于调用wait函数阻塞,否则释放互斥锁
4.条件变量通知(唤醒)操作
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 参数
    • cond:条件变量
  • 当条件满足,线程需要通知(唤醒)等待的线程
  • pthread_cond_signal函数通知(唤醒)单个线程
  • pthread_cond_broadcast函数通知(唤醒)所有线程
5.代码了解线程同步
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
/*
	一个线程赋值计算结果,一个线程负责获取结果
	当计算结果的线程没有执行完毕,获取结果的线程要等待(阻塞)
*/
typedef struct{//共享资源
    int res;//存放运算结果
    int is_wait;//用户给出用于判断的条件
    pthread_cond_t cond;//定义条件变量
    pthread_mutex_t mutex;//定义互斥锁
}Result;

//计算并将结果放置Result中的线程运行函数
void* set_fn(void *arg){
    int sum
    for(int i =1;i<=100;i++){
        sum +=i;
    }
    //将结果存放到Result
    Result *r = (Result*)arg;
    r->res = sum;
    
    //对两个线程共享的判断条件进行保护
    pthread_mutex_lock(&r-mutex);
    //判断获取结果的线程是否准备好,只有准备好了才可结束。is_wait为0代表没有准备好
    while(!r->is_wait){
        //等待获取结果的线程
        pthread_mutex_unlock(&r-mutex);
        Sleep(10000);
        pthread_mutex_lock(&r-mutex);
    }
    pthread_mutex_unlock(&r-mutex);
    //唤醒(通知)等待的那个获取结果的线程
    pthread_cond_broadcast(&r->cond)
    return(void*)0;
}

//获得结果的线程运行函数
void* get_fn(void *arg){
    Result *r = (Result*)arg;
    
    //对两个线程共享的判断条件进行保护(加锁)
    //两个线程对判断条件操作是互斥的
    pthread_mutex_lock(&r-mutex);
    //代表获取结果的线程已经准备好了
    r->is_wait = 1;
    
    //获取结果的线程等待,就是自身线程阻塞
    //&r->mutex这个锁是用于保护队列
    pthread_cond_wait(&r->cond,&r->mutex);
    //线程唤醒后,释放锁
    pthread_mutex_unlock(&r->mutex);
    int res = r->res;
    printf("ox%lx get sum is %d\n",pthread_self(),res);
    return(void*)0;
}

int main(void){
    int err;
	//定义线程标识符cal get
	pthread_t cal, get;
    
    //结构体赋值
    Result r;
    r.is_wait = 0;
    //条件变量,互斥锁初始化
    pthread_cond_init(&r.cond,null);
    pthread_mutex_init(&r.mutex,null);
    
    //创建两个线程,并给与赋值
    //启动获取结果的线程
	if ((err = pthread_create(&get, NULL, get_fn, (void*)&r)) != 0) {
		perror("pthread_create error");
	}
    //启动计算结果的线程
    if ((err = pthread_create(&cal, NULL, set_fn, (void*)&r)) != 0) {
		perror("pthread_create error");
	}
    
    //主线程要等两个子线程执行完毕
    pthread_join(get,null);
    pthread_join(cal,null);
    
    //条件变量和互斥锁的销毁
    pthread_cond_destroy(&r.cond);
    pthread_mutex_destroy(&r.mutex);
    return 0;
}

程序结果输出:

在这里插入图片描述

对pthread_cond_wait函数为什么在后面要加上 释放锁

//对两个线程共享的判断条件进行保护(加锁)
//两个线程对判断条件操作是互斥的
	pthread_mutex_lock(&r-mutex);
//代表获取结果的线程已经准备好了
	r->is_wait = 1;
    
//获取结果的线程等待,就是自身线程阻塞
//&r->mutex这个锁是用于保护队列
	pthread_cond_wait(&r-cond,&r->mutex);
//线程唤醒后,释放锁
	pthread_mutex_unlock(&r-mutex);

在这里插入图片描述

为什么pthread_cond_wait(&r-cond,&r->mutex)线程等待函数要在,释放锁函数的前面?

​ 正如图片所展示的,在执行pthread_cond_wait时第一步先锁释放(这个释放锁和上面那个加锁是一对的),同时又上锁(此时上锁是用于保护线程存放到等待队列),线程自己插入条件变量的等待队列中,此时再解锁等待唤醒,唤醒后在上锁和最后那个释放锁是一对的。

6.线程的状态转换

在这里插入图片描述

7.代码改进–从一对一到一对多
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
/*
	一个线程赋值计算结果,多个线程负责获取结果
	当计算结果的线程没有执行完毕,获取结果的线程要等待(阻塞)
*/
typedef struct{//共享资源
    int res;//存放运算结果
    int couter;//用于统计获取结果线程的数量
    pthread_cond_t cond;//定义条件变量
    pthread_mutex_t mutex;//定义互斥锁
}Result;

//计算并将结果放置Result中的线程运行函数
void* set_fn(void *arg){
    int sum
    for(int i =1;i<=100;i++){
        sum +=i;
    }
    //将结果存放到Result
    Result *r = (Result*)arg;
    r->res = sum;
    
    //对两个线程共享的判断条件进行保护
    pthread_mutex_lock(&r-mutex);
    //判断获取结果的线程是否多余2个,只有2个以上的线程了才可继续运行。否则就等待
    while(r->couter< 2){
        //等待获取结果的线程
        pthread_mutex_unlock(&r-mutex);
        Sleep(10000);
        pthread_mutex_lock(&r-mutex);
    }
    pthread_mutex_unlock(&r-mutex);
    //唤醒(通知)等待的那个获取结果的线程
    pthread_cond_broadcast(&r->cond)
    return(void*)0;
}

//获得结果的线程运行函数
void* get_fn(void *arg){
    Result *r = (Result*)arg;
    
    //对两个线程共享的判断条件进行保护(加锁)
    //两个线程对判断条件操作是互斥的
    pthread_mutex_lock(&r-mutex);
    //代表获取结果的线程已经准备好了
    r->couter ++;
    
    //获取结果的线程等待,就是自身线程阻塞
    //&r->mutex这个锁是用于保护队列
    pthread_cond_wait(&r-cond,&r->mutex);
    //线程唤醒后,释放锁
    pthread_mutex_unlock(&r-mutex);
    int res = r->res;
    printf("ox%lx get sum is %d\n",pthread_self(),res);
    return(void*)0;
}

int main(void){
    int err;
	//定义线程标识符cal get
	pthread_t cal,get1,get1;
    
    //结构体赋值
    Result r;
    r.couter = 0;
    //条件变量,互斥锁初始化
    pthread_cond_init(&r.cond,null);
    pthread_mutex_init(&r.mutex,null);
    
    //启动获取结果的线程两个
	if ((err = pthread_create(&get1, NULL, get_fn, (void*)&r)) != 0) {
		perror("pthread_create error");
	}
    if ((err = pthread_create(&get2, NULL, get_fn, (void*)&r)) != 0) {
		perror("pthread_create error");
	}
    //启动计算结果的线程
    if ((err = pthread_create(&cal, NULL, set_fn, (void*)&r)) != 0) {
		perror("pthread_create error");
	}
    
    //主线程要等两个子线程执行完毕
    pthread_join(get1,null);
    pthread_join(get2,null);
    pthread_join(cal,null);
    
    //条件变量和互斥锁的销毁
    pthread_cond_destroy(&r.cond);
    pthread_mutex_destroy(&r.mutex);
    return 0;
}

代码运行结果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Unknown To Known

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值