Linux系统编程系列之条件变量

news2024/11/17 11:47:54

一、什么是条件变量

        条件变量是一种同步互斥机制,通常与互斥锁一起使用以实现线程之间的通信和同步。

二、问题的引入

        先来看一个例子:小楠是一名在校学生,每个月都会从父母那里得到一笔生活费。现在她的钱花光了,想要去取钱。但是很显然取钱这样的事情不是想干就能干的,前提是卡里必须得有钱才行!于是小楠拿起手机一查发现:余额为¥0。现在她除了干瞪眼,唯一能干的事情也许只有一件:等。等到她爸妈汇了钱打电话通知她为止。

        但更进一步,即便是她爸妈汇了钱也打了电话通知了她,此刻她也不能一定保证能取到钱,因为与此同时她的众多兄弟姐妹(统统共用一个银行账号)很可能已经抢先一步将钱悉数取光了!因此当小楠收到爸妈的电话之后,需要再次确认是否有钱,才能取钱。

三、使用场景

        (1)、 生产者-消费者模式:多个线程生产数据,多个线程消费数据。消费者等待条件变量,当生产者生产数据时唤醒消费者。

        (2)、 服务器程序:服务器可以使用条件变量来实现多个客户端之间的同步操作。当客户端请求数据时,服务器可能需要等待某些资源准备好后才能响应,此时可以使用条件变量来等待资源就绪。

        (3)、 任务管理:当有多个线程需要执行任务时,可以使用条件变量来通知空闲的线程执行任务

        (4)、 等待输入:当需要等待用户输入时,可以使用条件变量等待用户输入。

        总之,当需要等待某些条件满足时,使用条件变量是很常见的一种方式。条件变量提供了一个有效的机制来等待和通知多个线程,以实现共享资源间的同步和互斥

四、相关的函数API接口

        1、初始化条件变量

// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数cond:待初始化的条件变量
        参数cond_attr:待初始化的条件变量的属性,一般始终为0

         2、销毁条件变量

// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数cond:待销毁的条件变量

        3、 进入等待队列

// 阻塞等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

// 接口说明
        返回值;成功返回0,失败返回-1
        参数cond:条件变量
        参数mutex:需要获取的互斥锁

// 有限定时间的等待
int pthread_cond_timedwait(pthread_cond_t *cond, 
                           pthread_mutex_t *mutex, 
                           const struct timespec *abstime);

// 接口说明
        返回值;成功返回0,失败返回-1
        参数cond:条件变量
        参数mutex:需要获取的互斥锁
        参数abstime:限定的时间

        4、唤醒等待队列 

// 唤醒全部在条件变量等待队列的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

// 唤醒一个在条件变量等待队列的线程
int pthread_cond_signal(pthread_cond_t *cond);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数cond:条件变量

五、案例

        使用条件变量结合互斥锁完成存钱和取钱的演示

// 条件变量的案例

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int money = 0;

pthread_mutex_t data_mutex; // 定义互斥锁变量
pthread_once_t data_mutex_once_init;    // 函数单例初始化变量
pthread_once_t data_mutex_once_destroy;    // 函数单例销毁变量

pthread_cond_t data_cond; // 定义条件变量
pthread_once_t data_cond_once_init;    // 函数单例初始化变量
pthread_once_t data_cond_once_destroy;    // 函数单例销毁变量

// 初始化互斥锁data_mutex
void data_mutex_init(void)
{
    pthread_mutex_init(&data_mutex, NULL);
}

// 销毁互斥锁data_mutex
void data_mutex_destroy(void)
{
    pthread_mutex_destroy(&data_mutex);
}

// 初始化条件变量data_cond
void data_cond_init(void)
{
    pthread_cond_init(&data_cond, NULL);
}

// 销毁条件变量data_cond
void data_cond_destroy(void)
{
    pthread_cond_destroy(&data_cond);
}

// 线程1的例程函数,用来取钱
void *get_routine(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    // 函数单例,本程序只会执行data_cond_init()一次
    pthread_once(&data_cond_once_init, data_cond_init);

    while(1)
    {
        printf("wait mutex to get money...\n");
        pthread_mutex_lock(&data_mutex);    // 阻塞等待有数据才可以申请成功,用来同步
        printf("get mutex to get money\n");

        // 判断余额是否大于100
        while(money < 100)
        {
            printf("get money fail, enter cond queue\n");

            // 这里先自动解锁data_mutex,等待被唤醒后,会自动上锁data_mutex
            pthread_cond_wait(&data_cond, &data_mutex);

            printf("wake up from cond queue\n");
        }

        money -= 100;
        printf("get money success\n");
        pthread_mutex_unlock(&data_mutex); // 解锁
     
        sleep(1); // 每隔1秒取100元
    }   

    // 函数单例,本程序只会执行data_mutex_destroy()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);

    // 函数单例,本程序只会执行data_cond_destroy()一次
    pthread_once(&data_cond_once_destroy, data_cond_destroy);
}

// 线程2的例程函数,用来存钱
void *give_routine(void *arg)
{
    printf("I am send_routine, my tid = %ld\n", pthread_self());

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    // 函数单例,本程序只会执行data_cond_init()一次
    pthread_once(&data_cond_once_init, data_cond_init);


    while(1)
    {
        pthread_mutex_lock(&data_mutex);
        money += 200;

        // printf()应该放到临界区外面,但是为了演示效果所以放在这里
        printf("current money is %d\n", money); 

        pthread_mutex_unlock(&data_mutex);  // 解锁,相当于给线程1发送信号

        pthread_cond_broadcast(&data_cond); // 唤醒条件等待队列中等待的线程

        sleep(3); // 每隔3秒存200元
    }

   // 函数单例,本程序只会执行data_mutex_destroy()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);

    // 函数单例,本程序只会执行data_cond_destroy()一次
    pthread_once(&data_cond_once_destroy, data_cond_destroy);
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;

    // 创建线程1,用来取钱
    errno = pthread_create(&tid1, NULL, get_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create get_routine success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create get_routine fail\n");
    }

    
    // 创建线程2,用来存钱,线程拥有分离属性
    errno = pthread_create(&tid2, NULL, give_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create give_routine success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create give_routine fail\n");
    }

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

六、总结

        条件变量通常用于多线程间共享资源的同步访问,一般要配合互斥锁来使用,可以结合案例来加深对条件变量的理解。

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

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

相关文章

Redis-缓存穿透,缓存击穿,缓存雪崩

缓存穿透&#xff0c;缓存击穿&#xff0c;缓存雪崩 缓存穿透处理方案解决方案1 缓存空数据解决方案2 布隆过滤器 缓存击穿处理方案解决方案 1 互斥锁解决方案2 逻辑过期 缓存雪崩处理方案解决方案 1 给不同的key的过期时间设置添加一个随机值&#xff0c;降低同一个时段大量ke…

柯桥生活口语学习,英语中初次见面,除了Nice to meet you,还能说什么?

第一印象非常重要。所以当你第一次见到某人时&#xff0c;留下一个好印象很重要&#xff0c;尤其是当你面对一个重要的工作或者面对某个对你来说可能非常特别的人时。 下面我列出了一些最常用的说“很高兴见到你”的表达方法&#xff0c;也包括对方的回答&#xff0c;除了nice …

活动报名与缴费小程序开发笔记一

项目背景 活动报名与缴费小程序的开发背景主要源于以下几个因素&#xff1a; 1.数字化时代的需求&#xff1a; 随着移动互联网和智能手机的普及&#xff0c;人们习惯使用手机进行各种活动。传统的纸质报名表格和线下缴费方式变得相对繁琐&#xff0c;而数字化报名与缴费小程序…

2023年-华为机试题库B卷(Python)【满分】

华为机试题库B卷 已于5月10号 更新为2023 B卷 &#xff08;2023-10-04 更新本文&#xff09; 华为机试有三道题目&#xff0c;前两道属于简单或中等题&#xff0c;分值为100分&#xff0c;第三道为中等或困难题&#xff0c;分值为200分。总分为 400 分&#xff0c;150分钟考试…

GKR+Groth16:更快的MiMC证明

1. 引言 Consensys团队Alexandre Belling等人2022年论文 Recursion over Public-Coin Interactive Proof Systems; Faster Hash Verification 中&#xff0c;提出了&#xff1a; 用GKR来证明MiMC哈希计算的完整性将GKR verifier嵌入到SNARK&#xff08;Groth16&#xff09;电…

【开发篇】十四、SpringBoot整合Quartz实现定时任务

文章目录 1、关于定时任务2、Java原生实现3、相关名词4、SpringBoot整合Quartz5、Quartz的通用配置6、关于QuartzJobBean7、关于调度器Scheduler的绑定8、Quartz持久化 1、关于定时任务 定时任务在实际开发中使用场景很多&#xff0c;比如&#xff1a; 年度报告各种统计报告某…

vs code 离线安装 CodeLLDB 包[Acquiring CodeLLDB platform package]

1. 问题描述 最近在配置使用vscode编译c&#xff0c;一打开vscode就弹出以下信息“Acquiring CodeLLDB platform package” 2. 问题原因 vscode在安装CodeLLDB插件时&#xff0c;速度太慢&#xff0c;一直不能成功 3. 解决方案&#xff1a; 离线下载 CodeLLDB插件&#xff0c…

前后端通信到底是怎样一个过程

前后端通信是怎样 前言&#xff1a;Http协议 超文本传输协议 规定&#xff1a;每一次前后端通信&#xff0c;前端需要主动向后端发出请求&#xff0c;后端接收到前端的请求后&#xff0c;可以给出响应 1、Http报文 浏览器向服务器发送请求时&#xff0c;请求本身就是信息&…

ROS导航——环境感知(激光雷达)

下载相关驱动包&#xff08;激光雷达厂商应该会给出&#xff09; 编译后可能会出现部分错误&#xff0c;以下是部分情况&#xff1a; &#xff08;1&#xff09; 移植功能包后出现c文件无法找到头文件的情况&#xff1a;解决链接 修改代码&#xff1a;&#xff08;以我的雷达为…

将pyc文件转换为py文件

1.首先将pip版本升级 pip install --upgrade pip 2.然后安装uncompyle6 pip install uncompyle6 3.在系统的环境变量中&#xff0c;添加“python_home” 4.在系统变量Path中添加&#xff1a; %python_home%\Scripts\ 5.运行下面的代码&#xff0c;就会在你.pyc对应文件夹…

腾讯云服务器完整建站过程(新手搭建网站教程)

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网分享使用腾讯云服务器建站教程&#xff0c;新手站长搭…

第二章 进程与线程 十九、管程

目录 一、定义 管程是一种特殊的软件模块&#xff0c;由以下部分组成&#xff1a; 二、管程的基本特征 三、使用管程解决生产者消费者问题 四、总结 一、定义 管程是一种特殊的软件模块&#xff0c;由以下部分组成&#xff1a; 1、局部于管程的共享数据结构说明;&#xf…

[QT编程系列-45]: 内存检测工具Dr.Memory在Windows上的使用实践与详解

目录 一、使用前的澄清 二、下载地址 三、功能概述 四、 使用方法与步骤 4.1 常见命令 4.2 命令选项详解 4.3 常见问题监测 4.3.1 内存泄露相关参数 4.4 结果输出参数 4.5 输出分析 一、使用前的澄清 &#xff08;1&#xff09;之前在https://blog.csdn.net/fengbin…

SNAP与Sen2Cor下载与安装

SNAP软件下载与安装 一、下载地址 首先进入网站 找到DOWNLOAD下载页&#xff0c; 安装完成后&#xff0c;界面如下 还需要再装一个Sen2cor下载好之后&#xff0c;解压到用户文件夹下 然后打开L2A_Process.bat文件 打开CMD&#xff0c;输入 cd C:\Users\lenovo\AppData\L…

【算法训练-二分查找 一】【基本二分】二分查找、在排序数组中查找元素的第一个和最后一个位置

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是螺旋矩阵&#xff0c;使用【二维数组】这个基本的数据结构来实现 二分查找【EASY】 从最简单的二分查找入手&#xff0c;进而开始解决一系列其变体…

【Linux基础】Linux云服务器(腾讯云、阿里云、华为云)环境部署 | 安装远程XShell | 基本账号管理(超详细教程)

&#x1f449; 系列专栏&#xff1a;【LLinux基础】 &#x1f648; 个人主页&#xff1a;sunnyll 目录 一、前言 二、 Linux环境安装 &#x1f4a6; Linux 环境的搭建方式 &#x1f4a6;如何购买云服务器 三、 安装远程控制XShell &#x1f4a6;下载 XShell &#x1f4…

阿里云对象存储OSS SDK的使用

官方文档 https://help.aliyun.com/zh/oss/developer-reference/java 准备工作 windows安装好JDK&#xff0c;这里使用JDK1.8为例 windows安装好IDEA&#xff0c;这里使用IDEA2022 登录阿里云控制台&#xff0c;通过免费试用OSS或开通OSS 步骤 配置访问凭证 有临时和长期…

STM32F4学习笔记读取芯片UID和MAC地址

一、简介 在嵌入式设备开发过程中有时会需要为设备设置唯一的ID用以标识设备唯一&#xff0c;比如要求同一总线上的所有设备ID不能重复&#xff0c;要求设备具体唯一的MAC地址等等。每个STM32微控制器都自带一个96位的唯一ID&#xff0c;这个ID在任何情况下都是唯一且不允许修…

谷歌地球引擎GEE账户注册的快速、百分百成功方法

本文介绍免费注册谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;账户的方便、快捷的最新方法&#xff1b;基于这一方法&#xff0c;只要我们创建一个谷歌Cloud Project&#xff0c;就可以直接访问GEE。 GEE在原本&#xff08;大概前几年的时候&#…