Linux线程同步:深度解析条件变量接口

devtools/2024/9/20 7:10:33/ 标签: java, jvm, 开发语言, linux, Linux编程, 多线程
🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

  • `🍑Linux线程同步`
    • `🐉条件变量---实现线程同步`
      • `💧同步概念与竞态条件`
      • `🐆条件变量接口`
            • *初始化函数*
            • *销毁条件变量*
            • *等待条件满足*
            • *唤醒等待函数*
      • *🦅为什么 pthread_cond_wait 需要互斥锁?*
          • *条件变量使用规范*


🍑Linux线程同步

🐉条件变量---实现线程同步

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

💧同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。

🐆条件变量接口

初始化函数

可以使用预定义的宏PTHREAD_COND_INITIALIZER来静态初始化全局的pthread_cond_t变量

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

  • 参数:
    • cond:要初始化的条件变量
    • attr:NULL
销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

  • 参数:
    • cond:要在这个条件变量上等待
    • mutex:互斥锁,调用 pthread_cond_wait 函数后,会自动释放持有的锁,待被唤醒后,函数调用成功返回了,会自动加锁
唤醒等待函数
  • 将指定条件变量下等待的全部线程唤醒:
    int pthread_cond_broadcast(pthread_cond_t *cond);

  • 将指定条件变量下等待的一个线程唤醒:
    int pthread_cond_signal(pthread_cond_t *cond);

🦅为什么 pthread_cond_wait 需要互斥锁?

1. 保护共享资源
多线程环境中,多个线程可能会同时访问和修改共享资源。这些共享资源通常被互斥锁保护,以确保在任何时候只有一个线程可以访问它们。当线程调用pthread_cond_wait时,它通常位于访问这些共享资源的临界区内。因此,需要互斥锁来确保在调用pthread_cond_wait之前和之后,共享资源的状态不会被其他线程意外修改。

2. 防止虚假唤醒
pthread_cond_wait函数可能会因为多种原因被唤醒,包括真实的条件变化(由pthread_cond_signal或pthread_cond_broadcast触发)或其他系统事件(如信号或中断)。这种非条件触发的唤醒被称为“虚假唤醒”。为了避免虚假唤醒导致的错误,线程在pthread_cond_wait返回后需要再次检查条件是否真正满足。这个检查过程需要互斥锁的保护,以防止在检查条件时共享资源被其他线程修改。

3. 原子性
在调用pthread_cond_wait之前,线程需要释放互斥锁,以便其他线程可以修改条件并发出信号。然而,仅仅释放锁并调用pthread_cond_wait并不是原子的,这意味着在释放锁和进入等待状态之间可能存在时间窗口,其他线程可能会修改条件。为了解决这个问题,pthread_cond_wait函数内部会先释放锁,然后等待条件变量的信号。当条件满足且线程被唤醒时,pthread_cond_wait会重新获取之前释放的互斥锁,以确保操作的原子性

4. 防止死锁
如果在调用pthread_cond_wait时没有持有互斥锁,或者在调用后没有重新获取互斥锁,都可能导致死锁或其他同步问题。例如,如果线程在检查条件后没有立即释放锁就调用pthread_cond_wait,那么它可能会因为已经持有锁而无法被pthread_cond_signal或pthread_cond_broadcast唤醒。同样,如果线程在pthread_cond_wait返回后没有重新获取锁,那么它可能会访问一个处于不一致状态的共享资源。


按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:

// 错误的设计 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } pthread_mutex_unlock(&mutex); 
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥锁,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥锁变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。
条件变量使用规范
  • 等待条件代码
pthread_mutex_lock(&mutex); while (条件为假) pthread_cond_wait(cond, mutex); //修改条件 pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
pthread_mutex_lock(&mutex); 
//设置条件为真 
pthread_cond_signal(cond); 
pthread_mutex_unlock(&mutex);

示例代码:

#include <unistd.h>
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <cstdlib>using namespace std;pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;void *Mastercore(void *args)
{sleep(3);cout << "Master start runing ..." << endl;string name = static_cast<const char *>(args);while (true){pthread_cond_broadcast(&gcond); //唤醒指定变量条件下等待的所有线程// pthread_cond_signal(&gcond); // 唤醒指定变量条件下等待的队首的线程cout << " Master 唤醒了一个线程 ... " << endl;sleep(1);}return nullptr;
}void *SlaverCore(void *args)
{string name = static_cast<const char *>(args);while (true){// 加锁pthread_mutex_lock(&gmutex);// 设定条件变量--这时是持有锁的,需要将锁传入,调用wait的时候,会自动解锁pthread_cond_wait(&gcond, &gmutex);cout << "wait is : " << name << endl;// 解锁pthread_mutex_unlock(&gmutex);}return nullptr;
}void StartMaster(vector<pthread_t> *threadsptr)
{pthread_t tid;int n = pthread_create(&tid, nullptr, Mastercore, (void *)"Master thread");if (n == 0){cout << "Master create sucess..." << endl;}threadsptr->emplace_back(tid);
}void StartSlaver(vector<pthread_t> *threadsptr, int n = 3)
{for (int i = 0; i < n; i++){pthread_t tid;char *name = new char[64];   //注意需要newsnprintf(name, 64, "Slaver-%d", i + 1);int n = pthread_create(&tid, nullptr, SlaverCore, (void *)name);if (0 == n){cout << "create " << name << " success..." << endl;}threadsptr->emplace_back(tid);}
}void WaitAll(vector<pthread_t> &threads)
{for (auto &t : threads){pthread_join(t, nullptr);}
}int main()
{vector<pthread_t> threads;StartMaster(&threads);StartSlaver(&threads, 5);WaitAll(threads);return 0;
}


http://www.ppmy.cn/devtools/112719.html

相关文章

JNI 详细介绍

一 介绍 java调⽤c&#xff0c;c代码可以通过JNIEnv执行java代码。 安卓NDK 已经对JNI环境进行了集成&#xff0c;我们可以通过android studio来快速搭建一个项目。 二 项目搭建 打开android studio 创建工程&#xff0c;创建工程选择模板Native C 三 模板格式介绍 生成的…

Python数据分析及可视化教程--商城订单为例-适用电商相关进行数据分析---亲测可用!!!!

前言:Python 是进行数据分析和可视化的强大工具,常用的库包括 Pandas、NumPy、Matplotlib 和 Seaborn。以下是一个基本的教程概述,介绍了如何使用这些库来进行数据分析和可视化: Python数据分析及可视化教程 1、 环境准备2、数据准备3、开始数据分析3.1、导入库3.2、加载数…

深入理解数据分析的使用流程:从数据准备到洞察挖掘

数据分析是企业和技术团队实现价值的核心。 5 秒内你能否让数据帮你做出决策&#xff1f; 通过本文&#xff0c;我们将深入探讨如何将原始数据转化为有意义的洞察&#xff0c;帮助你快速掌握数据分析的关键流程。 目录 数据分析的五个核心步骤1. 数据获取常用数据获取方式 2. 数…

《C++模板元编程:高效实现编译期斐波那契数列计算》

在 C的神秘世界里&#xff0c;模板元编程犹如一把神奇的钥匙&#xff0c;能打开许多高性能编程的大门。今天&#xff0c;我们就来深入探讨如何在 C的模板元编程中实现一个在编译期计算斐波那契数列的算法&#xff0c;同时确保在面对非常大的输入时不会导致编译时间过长。 一、…

【开发环境搭建】Macbook M1搭建Java开发环境

JDK 安装与配置 下载并安装 JDK&#xff1a; ARM64 DMG 安装包下载链接&#xff1a;JDK21 for Mac (ARM64)。双击下载的 DMG 文件&#xff0c;按照提示安装 JDK。 配置环境变量&#xff1a; 打开终端&#xff0c;使用 vim 编辑 .bash_profile 文件&#xff1a; vim ~/.bash_pr…

_Array类,类似于Vector,其实就是_string

例子&#xff1a; using namespace lf; using namespace std;int main() {_Array<int> a(10, -1);_Array<_string> s { _t("one"), _t("two") };_pcn(a);_pcn(s);} 结果&#xff1a; 源代码_Array.h&#xff1a; /***********************…

直播相关03-录制麦克风声音, ffmpeg 命名,使用命令行完成录音

一 ffmpeg 命令 ffmpeg arg1 arg2 -i arg3 arg4 arg5ffmpeg 全局参数 输入文件参数 -i 输入文件 输出文件参数 输出文件arg1&#xff1a;全局参数 arg2&#xff1a;输入文件参数 arg3&#xff1a;输入文件 arg4&#xff1a;输出文件参数 arg5&#xff1a;输出文件 二 ffprobe …

根据NVeloDocx Word模板引擎生成Word(四)

前面介绍了《E6低代码开发平台》的Word模版引擎NVeloDocx&#xff0c;实现了表单的基本字段、子表、单张图片、二维码、条形码怎么基于NVelocity脚本输出到Word文件&#xff0c;都是些比较简单且常用的需求。 本篇介绍怎么基于NVeloDocx在Word中插入图表&#xff0c;目前只支持…

HarmonyOS Next鸿蒙NDK使用示例

创建一个Native C项目 跟普通项目相比&#xff0c;主要区别是多了一个cpp文件夹、oh-package.json5中的dependencies引入还有build-profile.json5中的externalNativeOptions配置&#xff0c;abiFilters是支持的CPU架构&#xff0c;目前移动端项目只支持arm64-v8a、x86_64两种。…

笔试强训day07

在字符串中找出连续最长的数字串 #include <bits/stdc.h>using namespace std; const int N 500; char s[N]; bool check(char c) {return c > 0 && c < 9; } int main() {scanf("%s", s);int l -1, r -1;int n strlen(s);int left 0, rig…

Spring Boot 常用注解

1. 基础 Spring 注解 Component 标记一个类作为 Spring IoC 容器的一个组件。Repository 标记一个 DAO 类&#xff0c;同时提供了异常转换机制。Service 标记业务逻辑层的服务类。Controller 标记一个 Web 层的控制器类。RestController 结合了 Controller 和 ResponseBody&am…

GO Govaluate

govaluate 是一个用于在 Go 语言中动态求值表达式的库。它允许你解析和评估字符串形式的表达式&#xff0c;这些表达式可以包含变量、函数以及逻辑、算术和比较操作。它非常适合在运行时处理复杂的逻辑规则和条件表达式&#xff0c;而不需要重新编译代码。 安装 govaluate go…

C语言自定义类型结构体(24)

文章目录 前言一、结构体类型的声明结构体回顾结构体的特殊声明结构体的自引用 二、结构体的内存对齐对齐规则为什么存在内存对齐&#xff1f;修改默认对齐数 三、结构体传参四、结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段的应用位段使用的注意事项 总结 前言…

Linux学习-Ansible(一)

环境- Rocky-Linux8.6 安装部署Ansible # 安装ansible [rootharbor ansible]# dnf install -y ansible-core #查看安装信息 [rootharbor ansible]# ansible-doc --version ansible-doc [core 2.12.2]config file /root/ansible/ansible.cfgconfigured module search path […

动态规划---不相交的线

题目&#xff1a; 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非水…

SQLITE3数据库实现信息的增删改查

#include <myhead.h> #include <sqlite3.h> typedef struct { int id; char name[20]; int age; int money; }woker; int callbake(void *arg,int n,char **a,char **b)//回调 输出查找到的工人信息 { for(int i 0;i<n;i) { …

[数据集][目标检测]汽车头部尾部检测数据集VOC+YOLO格式5319张3类别

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 版权单位&#xff1a;未来自主研究中心(FIRC) 版权声明&#xff1a;数据集仅仅供个人使用&#xff0c;不得在未授权情况下挂淘宝、咸鱼等交易网站公开售卖,由此引发的法律责任需自行承担 数据集格式&#xff1a;Pascal VOC格…

Linux05

1.echo命令 echo是输出命令&#xff0c;类似printf 例如&#xff1a;echo "hello world"&#xff0c;输出hello world echo pwd&#xff0c;输出pwd的位置。是键盘上~ 2.重定向符> >> >指把左边内容覆盖到右边 echo hello world>test.txt >…

MATLAB在嵌入式系统设计中的最佳实践

嵌入式系统设计是一个复杂的过程&#xff0c;涉及硬件和软件的紧密集成。MATLAB提供了一套全面的解决方案&#xff0c;从算法开发到代码生成&#xff0c;再到硬件验证&#xff0c;极大地简化了这一过程。本文将探讨使用MATLAB进行嵌入式系统设计的最佳实践&#xff0c;包括模型…

Vue Router push方法的使用

Vue Router push方法的使用 this.$router.push 是 Vue Router 提供的一个方法,用于在 Vue.js 应用中进行编程式导航。它的作用是将用户导航到应用中的不同路由。 基本作用 this.$router.push 方法会在浏览器历史记录中添加一个新的记录,并导航到指定的路由。它的工作方式类…