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

news2024/11/17 6:01:02

目录

        • 1.条件变量基本概念
        • 2.条件变量创建和销毁
        • 3.条件变量等待操作
        • 4.条件变量通知(唤醒)操作
        • 5.代码了解线程同步
        • 6.线程的状态转换
        • 7.代码改进--从一对一到一对多

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;
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55535.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kamiya丨Kamiya艾美捷抗FLAG多克隆说明书

Kamiya艾美捷抗FLAG多克隆化学性质&#xff1a; 程序&#xff1a;用FLAG肽免疫家兔与KLH偶联。多次免疫后在弗氏佐剂中收集血清使用固定在固相上的肽。 规范&#xff1a; 使用氨基末端分析抗体Met FLAG BAP、氨基末端FLAG-BAP和羧基末端FLAG-BAP融合蛋白和Invitrogen Posite…

跳槽,从这一个坑,跳进另外一个坑

软件测试员跳槽有一个奇怪的现象&#xff1a;那些跳槽的测试员们&#xff0c;总是从一个坑&#xff0c;跳进另一个坑中&#xff0c;无论怎么折腾&#xff0c;也没能拿到更好的offer&#xff0c;更别说&#xff0c;薪资实现爆炸式增长&#xff0c;自身价值得到升华~ 在如今经验…

【Web安全】注入攻击

目录 前言 1、注入攻击 1.1 SQL注入 1.2 数据库攻击技巧 1.2.1 常见的攻击技巧 1.2.2 命令执行 1.2.3 攻击存储过程 1.2.4 编码问题 1.2.5 SQL Column Truncation 1.3 正确防御SQL注入 1.4 其他注入攻击 1.4.1 XML注入 1.4.2 代码注入 1.4.3 CRLF注入 前言 年…

Kotlin高仿微信-第53篇-添加好友

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

数商云SRM系统询比价有何优势?供应商平台助力汽车零部件企业快速查找供应商

随着中国汽车行业的高速发展、汽车保有量的增加以及汽车零部件市场的扩大&#xff0c;我国汽车零部件行业得到了迅速发展&#xff0c;增长速度整体高于我国整车行业。数据显示&#xff0c;我国汽车零部件的销售收入从2016年3.46万亿元增长至2020年的4.57万亿元&#xff0c;年均…

世界杯——手动为梅西标名

梅西的铁粉来集赞啦。 今天带来了一个为图片添加字样的小功能&#xff0c;我们的测试目标图片是&#xff1a; 我们的测试目标是&#xff1a; 我们使用的是Python语言&#xff0c;使用了Image包用作图片处理&#xff0c;matplotlib包用作坐标查阅&#xff0c;这个坐标还是很好看…

微服务框架 SpringCloud微服务架构 8 Gateway 网关 8.5 全局过滤器

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构8 Gateway 网关8.5 全局过滤器8.5.1 全局过滤器 GlobalFilter8.5.2 案例8.…

segmentation

(用于医学图像分割的金字塔医学transformer) Pyramid Medical Transformer for Medical Image Segmentation 基于CNN的模型通过低效地堆叠卷积层来捕获长期依赖性&#xff0c;但基于注意力的模型明确地构建了所有范围的关系。然而&#xff0c;为全局关系分配可学习参数是昂贵…

WebAPI项目搭建及其发布测试

目录 1.创建项目 2.配置 3.发布 4.测试页面 1.创建项目 &#xff08;1&#xff09;创建ASP.NET Web应用程式&#xff0c;如下图&#xff1a; &#xff08;2&#xff09;选择Empty,勾选Web API&#xff0c;点击确定创建&#xff0c;如下图&#xff1a; &#xff08;3&#x…

解决Netty那些事儿之Reactor在Netty中的实现(创建篇)-上

本系列Netty源码解析文章基于 4.1.56.Final版本 在上篇文章深入讲解Netty那些事儿之从内核角度看IO模型中我们花了大量的篇幅来从内核角度详细讲述了五种IO模型的演进过程以及ReactorIO线程模型的底层基石IO多路复用技术在内核中的实现原理。 最后我们引出了netty中使用的主从…

代码随想录刷题day52 300.最长递增子序列;674. 最长连续递增序列;718. 最长重复子数组

代码随想录刷题day52 300.最长递增子序列&#xff1b;674. 最长连续递增序列&#xff1b;718. 最长重复子数组 二维dp的初次应用&#xff0c;关于子序列的一系列问题。 300.最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;Leetcode&#xff09; 子序列的一个入门题…

java中set接口、哈希表、哈希值、HashSet、LinkedHashSet、方法的可变参数

set接口&#xff1a; set接口和list接口一样&#xff0c;都是继承于Collection接口&#xff0c;它与Collection接口中的方法基本一致。特点&#xff1a;不允许存储重复元素&#xff0c;元素没有索引。它主要有两个实现类&#xff1a;HashSet&#xff08;具有哈希表结构&#x…

微服务框架 SpringCloud微服务架构 9 初识 Docker 9.2 Docker 与虚拟机的差别

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构9 初识 Docker9.2 Docker 与虚拟机的差别9.2.1 Docker 与虚拟机9.2.2 总结…

基于Android的二维码识别系统的研究与实现(eclipse开发)

目 录 1 Android系统开发背景与意义 1 1.1 Android系统平台的出现 1 1.2 Android系统的发展 1 1.3 Android系统架构的介绍 1 1.4 Android开放系统 3 1.5 Android系统的特点 3 2 二维码识别系统背景介绍 3 2.1 二维码识别系统背景 3 2.1.1 二维码技术产生的背景 3 2.1.2 二维码分…

图像压缩原理-JPEG

搬来一个基础啊 给自己看~~ 非技术指正勿扰 图像的格式有很多种&#xff0c;比如PNG&#xff0c;JPEG等等&#xff0c;但当我们把一张图用工具变成各种其他格式时&#xff0c;其在计算机文件系统显示的文件大小各不一样&#xff0c;但是当你打开显示时&#xff0c;从视觉角度…

Android进阶之路 - Json解析异常

在App与H5交互时&#xff0c;有一个调原生分享的需求&#xff0c;交互方面没有问题&#xff0c;因为分享需要多值&#xff0c;所以采用json进行传递&#xff0c;在app接收进行解析时遇到了这个解析异常 Value &#xfeff; of type java.lang.String cannot be converted to JS…

【操作系统一】图解TCP/IP模型+实战

【操作系统一】OSI模型和TCP/IP模型一、OSI模型1、什么是OSI模型2、osi七层参考模型3、我更想介绍TCP/IP模型二、TCP/IP模型1、TCP/IP模型起源2、TCP/IP模型是五层还是四层&#xff1f;3、两层TCP/IP模型4、传输层&#xff08;也叫传输控制层&#xff09;4.1 TCP4.3、三次握手4…

【算法面试题汇总】LeetBook列表的算法面试题汇总---动态规划题目及答案

整理不易留个小心心呗&#x1f970; 如果有更好的或者是我有错的地方还请各位大佬指出哦 有些是copy的还望不要介意 动态规划至少有k个重复字符的最长子串二叉树中的最大路径和最长连续序列打家劫舍完全平方数最长上升子序列*零钱兑换矩阵中的最长递增路径至少有k个重复字符的最…

java计算机毕业设计ssm民宿管理系统设计7lky4(附源码、数据库)

java计算机毕业设计ssm民宿管理系统设计7lky4&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff0…

Linux服务器上Neo4j的安装、迁移

目录 1、环境 2、下载 3、修改配置 4、启动及其他命令 5、客户端访问&#xff1a; 6、数据迁移 1、环境 Neo4j是基于Java的图形数据库&#xff0c;运行Neo4j需要启动JVM进程&#xff0c;因此必须安装JAVA SE的JDK。 neo4j版本&#xff1a;neo4j-community-3.5.6 2、下…