《OSTEP》条件变量(chap30)

〇、前言

本文是对《OSTEP》第三十章的实践与总结。

一、条件变量

#include <pthread.h>
#include <stdio.h>
#include <assert.h>

int buffer;
int count = 0; // 资源为空

// 生产,在 buffer 中放入一个值
void put(int value) {
    assert(count == 0);
    count = 1;
    buffer = value;
}
// 消费,取出 buffer 中的值
int get() {
    assert(count == 1);
    count = 0;
    return buffer;
}

/***********第一版本**********/
// 生产者
void *producer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        put(i);
    }
    return NULL;
}
// 消费者
void *consumer(void *arg) {
    while (1) {
        int temp = get();
        printf("消费的值:%d\n", temp);
    }
    return NULL;
}

int main() {
    pthread_t p1, p2;
    int arg = 100;
    pthread_create(&p1, NULL, producer, &arg);
    pthread_create(&p2, NULL, consumer, NULL);
    // 等待两个线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    return 0;
}

运行:

*** chap30_条件变量 % gcc -o a con_prodece.c
*** chap30_条件变量 % ./a
Assertion failed: (count == 0), function put, file con_prodece.c, line 10.
消费的值:0
Assertion failed: (count == 1), function get, file con_prodece.c, line 16.
zsh: abort      ./a

可以看到,断言直接失败。

pthread_cond_t cond;
pthread_mutex_t mutex;
/***********第二版本**********/
// 生产者
void *producer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if (count == 1) {
            pthread_cond_wait(&cond, &mutex);
        }
        put(i);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    return NULL;
}
// 消费者
void *consumer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if (count == 0) {
            pthread_cond_wait(&cond, &mutex);
        }
        int temp = get();
        pthread_mutex_unlock(&mutex);
        printf("消费:%d\n", temp);
        pthread_cond_signal(&cond); 
    }
    return NULL;
}

int main() {

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t p1, p2;
    int arg = 100;
    pthread_create(&p1, NULL, producer, &arg);
    pthread_create(&p2, NULL, consumer, &arg);
    // 等待两个线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:

*** chap30_条件变量 % ./a                    
消费:0
消费:1
消费:2
消费:3
...
消费:95
消费:96
消费:97
消费:98
消费:99

可以看到,在两个线程的情况下,工作的很好。

/***********第三版本**********/
// 生产者
void *producer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if (count == 1) {
            pthread_cond_wait(&cond, &mutex);
        }
        put(i);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    return NULL;
}
// 消费者
void *consumer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if (count == 0) {
            pthread_cond_wait(&cond, &mutex);
        }
        int temp = get();
        pthread_mutex_unlock(&mutex);
        printf("消费:%d\n", temp);
        pthread_cond_signal(&cond); 
    }
    return NULL;
}

int main() {

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t p1, p2, p3;
    int arg = 100;
    int arg1 = 50;
    int arg2 = 50;
    pthread_create(&p1, NULL, producer, &arg);
    pthread_create(&p2, NULL, consumer, &arg1);
    pthread_create(&p3, NULL, consumer, &arg2);
    // 等待两个线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(p3, NULL);
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:

*** chap30_条件变量 % gcc -o a con_prodece2.c
*** chap30_条件变量 % ./a                    
消费:0
消费:1
Assertion failed: (count == 1), function get, file con_prodece2.c, line 18.
zsh: abort      ./a

可以看到,再增加了一个消费线程之后,出现了断言错误。这是因为出现了假唤醒,使得某个线程醒来后,断言错误。解决方法很简单,直接将 if()换成 while():

/***********第三版本**********/
// 解决虚假唤醒
// 生产者
void *producer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 1) {
            pthread_cond_wait(&cond, &mutex);
        }
        put(i);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    return NULL;
}
// 消费者
void *consumer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            pthread_cond_wait(&cond, &mutex);
        }
        int temp = get();
        pthread_mutex_unlock(&mutex);
        printf("消费:%d\n", temp);
        pthread_cond_signal(&cond); // 在解锁之后发出信号
    }
    return NULL;
}

int main() {

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t p1, p2, p3;
    int arg = 100;
    int arg1 = 50;
    int arg2 = 50;
    pthread_create(&p1, NULL, producer, &arg);
    pthread_create(&p2, NULL, consumer, &arg1);
    pthread_create(&p3, NULL, consumer, &arg2);
    // 等待两个线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(p3, NULL);
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:

luliang@shenjian chap30_条件变量 % ./a
消费:0
消费:2
消费:1
消费:3
消费:4
消费:5
...

可以看到会卡住,这其实是由于三个线程都睡眠导致的,这种情况是怎么发生的呢?
假设生产者唤醒了第一个消费者,消费者又恰巧唤醒了第二个生产者,第二个生产者被唤醒之后又睡眠。这样三个线程都在睡眠。解决问题的办法就是消费者只能唤醒生产者,生产者只能唤醒消费者。以下就是终极版本的代码:

#include <assert.h>
#include <pthread.h>
#include <stdio.h>

int buffer;
int count = 0; // 资源为空
pthread_cond_t cond_consumer;
pthread_cond_t cond_procedure;
pthread_mutex_t mutex;

// 生产,在 buffer 中放入一个值
void put(int value) {
    assert(count == 0);
    count = 1;
    buffer = value;
}
// 消费,取出 buffer 中的值
int get() {
    assert(count == 1);
    count = 0;
    return buffer;
}

/***********第四版本**********/
// 假设生产者唤醒了第一个消费者,消费者又唤醒了第二个生产者,第二个生产者
// 之后又睡眠.这样三个线程都在睡眠.
// 核心问题就是,消费者只能唤醒生产者,生产者只能唤醒消费者.
// 生产者
void *producer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 1) {
            pthread_cond_wait(&cond_procedure, &mutex);
        }
        put(i);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond_consumer);

    }
    return NULL;
}
// 消费者
void *consumer(void *arg) {
    int loops = *((int *)arg);
    for (int i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);

        while (count == 0) {
            pthread_cond_wait(&cond_consumer, &mutex);
        }
        int temp = get();
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond_procedure);
        printf("消费:%d\n", temp);
    }
    return NULL;
}

int main() {

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond_consumer, NULL);
    pthread_cond_init(&cond_procedure, NULL);

    pthread_t p1, p2, p3;
    int arg = 100;
    int arg1 = 50;
    int arg2 = 50;
    pthread_create(&p1, NULL, producer, &arg);
    pthread_create(&p2, NULL, consumer, &arg1);
    pthread_create(&p3, NULL, consumer, &arg2);
    // 等待线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(p3, NULL);
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_consumer);
    pthread_cond_destroy(&cond_procedure);
    return 0;
}

运行结果:

*** chap30_条件变量 % gcc -o a con_prodece3.c
*** chap30_条件变量 % ./a
消费:0
消费:2
消费:1
...
消费:97
消费:98
消费:99

可以看到运行得很好,成功地解决了并发、虚假唤醒以及全部线程都睡眠的情况。

二、总结

我们看到了引入锁之外的另一个重要同步原语:条件变量。当某些程序状态不符合要求时,通过允许线程进入休眠状态,条件变量使我们能够漂亮地解决许多重要的同步问题,包括著名的(仍然重要的)生产者/消费者问题,以及覆盖条件。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Zephyr-7B论文解析及全量训练、Lora训练

文章目录 一、Zephyr&#xff1a;Direct Distillation of LM Alignment1.1 开发经过1.1.1 Zephyr-7B-alpha1.1.2 Zephyr-7B-beta 1.2 摘要1.3 相关工作1.4 算法1.4.1 蒸馏监督微调&#xff08;dSFT&#xff09;1.4.2 基于偏好的AI反馈 (AIF&#xff09;1.4.3 直接蒸馏偏好优化&…

赛氪助力全国大学生数学竞赛山东赛区圆满举办

近日&#xff0c;全国大学生数学竞赛山东赛区比赛有序进行&#xff0c;赛氪已连续6年助力本项赛事蓬勃发展。在中国高等教育学会高校竞赛评估与管理体系研究专家工作组发布的《2022全国普通高校大学生竞赛分析报告》中&#xff0c;本赛事荣登观察目录。 全国大学生数学竞赛旨在…

Transforme原理--全局解读

文章目录 作用全局解读 作用 Transformer最初设计用于处理序列数据&#xff0c;特别在NLP(自然语言处理)领域取得了巨大成功 全局解读 Transformer来源于谷歌的一篇经典论文Attention is All you Need 在此使用Transformer在机器翻译中的运用来讲解Transformer。 其中Tran…

Windows11跳过联网激活 跳过登陆操作

1 背景 笔者使用VirtualBox时安装Win11&#xff0c;初始化的配置提示需要注册账户才能进行下一步操作&#xff0c;于是去查了一下发现有办法绕过&#xff0c;方法就是断网oobe\ByPassNRO.cmd&#xff0c;试了一下发现可以&#xff0c;便有了这篇文章。 2 流程 开机之前&…

【完美世界】石昊负伤遭囚禁,无始种惊现,二秃子用柳枝力保石昊

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 深度爆料完美世界最新预告资讯&#xff0c;《完美世界》第137集预告片已经更新了&#xff0c;这一集的预告片充满了紧张的气氛和精彩的情节。从预告中我们可以看到&#xff0c;石昊的真实身份被天人族知晓&…

3.30每日一题(多元函数微分学)

1、判断连续&#xff1a;再分界点的极限值等于该点的函数值&#xff1b; 如何求极限值&#xff1a; 初步判断&#xff1a;分母都为二次幂开根号&#xff0c;所以分母为一次幂&#xff1b;分子为二次&#xff0c;一般来说整体为0&#xff1b; 如何说明极限为零&#xff08;常用…

ZYNQ_project:IP_ram_pll_test

例化MMCM ip核&#xff0c;产生100Mhz&#xff0c;100Mhz并相位偏移180&#xff0c;50Mhz&#xff0c;25Mhz的时钟信号。 例化单口ram&#xff0c;并编写读写控制器&#xff0c;实现32个数据的写入与读出。 模块框图&#xff1a; 代码&#xff1a; module ip_top(input …

SpringBoot_01

Spring https://spring.io/ SpringBoot可以帮助我们非常快速的构建应用程序、简化开发、提高效率。 SpringBootWeb入门 需求&#xff1a;使用SpringBoot开发一个web应用&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串"Hello World~~~"。 步骤…

测试人员如何通过AI提高工作效率!

随着AI技术的兴起&#xff0c;像OpenAI推出的ChatGPT、Microsoft发布的Microsoft 365 Copilot、阿里的通义千问、百度的文心一言、华为的盘古大模型等。很多测试人员开始担心&#xff0c;岗位是否会被AI取代&#xff1f;其实取代你的不是AI&#xff0c;而是会使用AI的测试人&am…

基于springboot+vue的校园闲置物品交易系统

运行环境 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven 项目介绍 本文从管…

自动驾驶学习笔记(七)——感知融合

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 感知融合 卡尔曼滤波 融合策略 实…

NtripShare Mos地铁自动化监测终端盒子硬件设计

自动化监测产品到目前为止做了接近一年&#xff0c;在软件层面上&#xff0c;控制终端软件、平台软件、网平差算法都已解决&#xff0c;硬件盒子始终是心里过不去的坎&#xff0c;最终还是没有耐住性子自己做了一把。 选型如下&#xff1a; 1、主板:瑞芯微RK3568主板。 2、外…

解决《荒野大镖客》提示emp.dll文件丢失问题,总结5个修复方法

在当今数字时代&#xff0c;游戏已经成为人们休闲娱乐的重要方式。作为一名游戏爱好者&#xff0c;笔者在近期体验《荒野大镖客》这款游戏时&#xff0c;遇到了一个令人苦恼的问题——emp.dll文件丢失。这个问题让游戏的无法启动进行。本文将围绕这一问题&#xff0c;探讨其原因…

Leetcode2834. 找出美丽数组的最小和

Every day a Leetcode 题目来源&#xff1a;2834. 找出美丽数组的最小和 解法1&#xff1a;贪心 从最小正整数 1 开始枚举&#xff0c;设当前数为 num&#xff0c;如果 nums 里没有 target - num&#xff0c;就说明可以添加 num&#xff0c;依次填满直到有 n 个数即可。 用…

公开数据集:灵长类动物多通道感觉运动皮层电生理学的研究

Nonhuman Primate Reaching with Multichannel Sensorimotor Cortex Electrophysiology. 1 公开数据集网址&#xff1a;https://zenodo.org/records/3854034 目录 General DescriptionPossible usesVariable namesDecoder ResultsVideosSupplementsContact InformationCitation…

java 类和对象 (图文搭配,万字详解!!)

关于java类和对象&#xff0c;我们要掌握几个重点&#xff01; 1.类的定义方式以及对象的实例化 2.类中的成员变量和成员方法的使用 3.对象的整个初始化过程 4.封装特性 5.代码块 目录 一、面向对象的初步认识 1.1 什么是面向对象 1.2 面向对象与面向过程 1.2.1传统洗…

Python:词法分析(行结构与显式、隐式行拼接)

相关阅读 Pythonhttps://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482 1、逻辑结构 一个Python程序由许多逻辑行组成&#xff0c;字面意义上的一行指的是末尾有换行符(\n)&#xff0c;但在不同的情况下&#xff0c;行末尾的换行符(\n)可能有…

语音识别与自然语言处理(NLP):技术前沿与未来趋势

语音识别与自然语言处理&#xff08;NLP&#xff09;&#xff1a;技术前沿与未来趋势 随着科技的快速发展&#xff0c;语音识别与自然语言处理&#xff08;NLP&#xff09;技术逐渐成为人工智能领域的研究热点。这两项技术的结合&#xff0c;使得机器能够更好地理解和处理人类语…

解析html生成Word文档

内容&#xff1a;读取html文件中的文本内容&#xff0c;然后生成Word文档导出。 事例场景&#xff1a;需求开发完成之后需要写文档&#xff08;代码修改清单&#xff09;&#xff0c;文档内容就是这次需求修改/新增的所有代码&#xff0c;需要列出修改的文件路径以及代码片段&…

Dart笔记:一些代码生成工具站点的介绍

Dart笔记&#xff1a; 一些代码生成工具站点的介绍 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/1343…
最新文章