【Linux】< 条件变量等待>解决< 线程饥饿问题 >——【多线程同步问题】

ops/2024/10/20 12:58:08/

前言

大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
在这里插入图片描述

欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!

  • YY的《C++》专栏
  • YY的《C++11》专栏
  • YY的《Linux》专栏
  • YY的《数据结构》专栏
  • YY的《C语言基础》专栏
  • YY的《初学者易错点》专栏
  • YY的《小小知识点》专栏
  • YY的《单片机期末速过》专栏
  • YY的《C++期末速过》专栏
  • YY的《单片机》专栏
  • YY的《STM32》专栏
  • YY的《数据库》专栏
  • YY的《数据库原理》专栏

目录

  • 一.<同步>概念&<条件变量>基本概念
  • 二.<条件变量等待> 为什么一定需要 <互斥锁 >?
  • 三.<条件变量>使用规范
  • 四.饥饿问题展示——"直到其他线程改变前,一直处于忙等待"
    • 1.基于【普通队列】的<生产者消费者模型>面临的<线程饥饿问题>
    • 2.基于【阻塞队列】的<生产者消费者模型>解决<线程饥饿问题>
    • 3.<条件变量>实现【阻塞队列】设计部分(图文详细解读)
    • 4.<条件变量>实现【阻塞队列】代码
  • 五.条件变量语法
    • 1.条件变量用到的头文件
    • 2.条件变量的初始化(动态&静态)
    • 3.条件变量的销毁
    • 4.条件变量的等待
    • 5.唤醒(一般是其他线程中)条件变量的等待

一.<同步>概念&<条件变量>基本概念

  • 同步: 在保证数据安全的前提下,让线程能够 按照某种 特定的顺序 访问临界资源 ,从而有效避免 饥饿问题
  • 条件变量: 利用线程间共享的全局变量进行同步的一种机制 它允许一个或多个线程在某个条件满足时进行等待,并在条件满足时被唤醒
  • 注意:条件变量本身不是锁,而是与互斥锁(Mutex)结合使用,以确保线程安全

二.<条件变量等待> 为什么一定需要 <互斥锁 >?

  1. 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以 必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足 ,并且友好的通知等待在条件变量上的线程。
  2. 条件不会无缘无故的突然变得满足了,必然会 牵扯到共享数据的变化 所以一定要用互斥锁来保护 。没有互斥锁就无法安全的获取和修改共享数据

三.<条件变量>使用规范

  • 等待条件代码
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);

四.饥饿问题展示——“直到其他线程改变前,一直处于忙等待”

1.基于【普通队列】的<生产者消费者模型>面临的<线程饥饿问题>

  • 我们有这样一个场景:
  • 当一个线程 互斥地 访问某个变量时,它可能发现 在其它线程 改变状态 之前,它什么也做不了。
  • 例如:在下面的 生产者消费者(普通队列)模型中 一个线程访问 队列 时,发现队列为空,它 只能等待(忙等待) ,直到其它线程将一个节点添加到队列中
  • 这种情况就需要用到条件变量
    在这里插入图片描述

2.基于【阻塞队列】的<生产者消费者模型>解决<线程饥饿问题>

  • 上面提到: 生产者消费者(普通队列)模型 会面临 <线程饥饿问题>
  • 阻塞队列 则解决了这个问题,用到了 <条件变量>
  • <条件变量> : 条件变量是利用线程间共享的全局变量进行同步的一种机制。它允许一个或多个线程在某个条件满足时进行等待,并在条件满足时被唤醒。条件变量本身不是锁,而是与互斥锁(Mutex)结合使用,以确保线程安全
  • 下面是BlockingQueue的机制:
  • 当队列为空时:从队列获取元素的操作将会被 阻塞,直到队列中被放入了元素;
  • 当队列满时:往队列里存放元素的操作也会被 阻塞,直到有元素被从队列中取出
  • 多线程编程中阻塞队列(Blocking Queue) 解决了 <线程饥饿问题>
    在这里插入图片描述

3.<条件变量>实现【阻塞队列】设计部分(图文详细解读)

  • 我们如图所示,在入队列和出队列处分别设置 互斥量(锁) 条件变量(锁)
    在这里插入图片描述

我们拿线程1入队列过程举例:

  • 队列满了,在1号条件变量上等待;
  • 队列空了,在2号条件变量上等待
  1. 线程1生产资源进入队列,上互斥锁,发现不符合1号条件变量的条件(队列没满),程序往下跑,解除互斥锁
  2. 线程1生产资源进入队列,上互斥锁, 发现符合1号条件变量(队列满了),在条件变量上等待 ,程序不往下跑
  3. 此时,线程2消费资源出队列,上互斥锁,发现符合2号条件变量(队列非空),程序往下跑,解除互斥锁; 此时给1号线程条件变量发送信号,唤醒1号条件变量,告诉他“ 你不用等了 ”
  4. 此时线程1的条件变量被唤醒,程序往下跑,解除互斥锁

4.<条件变量>实现【阻塞队列】代码

  • 回顾阻塞队列:
  • 注:这里很多条件变量函数我们还没有介绍,我们明白其作用即可 ,会放到博客最后
  • 当队列为空时:从队列获取元素的操作将会被 阻塞,直到队列中被放入了元素;
  • 当队列满时:往队列里存放元素的操作也会被 阻塞,直到有元素被从队列中取出
  • 代码实现如下:
  • 只展示出入队列部分,完整版本在最后

//_c_cond消费者consumer的条件变量
//_p_cond生产者productor的条件变量
//_q是阻塞队列void Push(const T &in)//生产者productor
{pthread_mutex_lock(&_mutex);if(IsFull())//判断是否满,满了进入阻塞等待{//自己,阻塞等待pthread_cond_wait(&_p_cond,&_mutex);}_q.push(in);//生产//生产了,另一个线程条件变量不符合了,唤醒另一个线程的条件变量阻塞等待pthread_cond_signal(&_c_cond);pthread_mutex_unlock(&_mutex);
}void Pop(T *out)//消费者consumer
{pthread_mutex_lock(&_mutex);if(IsEmpty())//判断是否空,空了进入阻塞等待{//自己,阻塞等待pthread_cond_wait(&_c_cond,&_mutex);//伪唤醒状态}*out=_q.front();_q.pop();//消费//消费了,另一个线程条件变量不符合了,唤醒另一个线程的条件变量阻塞等待pthread_cond_signal(&_p_cond);pthread_mutex_unlock(&_mutex);
}

五.条件变量语法

1.条件变量用到的头文件

  • 线程库中有 互斥锁 和 条件变量
#include <stdio.h>  
#include <pthread.h> 

2.条件变量的初始化(动态&静态)

初始化条件变量有两种方法:静态初始化和动态初始化

  • 方法1, 静态初始化:
  • 静态初始化的互斥量 不需要 显式调用pthread_cond_destroy函数进行销毁
pthread_cond_t cond_d = PTHREAD_COND_INITIALIZER;
  • 方法2, 动态初始化:
  • 动态初始化的条件变量在使用完毕后需要显式调用pthread_cond_destroy函数进行销毁
pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:cond:要初始化的条件变量attr:条件变量使用的属性对象,通常传递NULL表示使用默认属性。

3.条件变量的销毁

  • 动态初始化的条件变量在使用完毕后需要显式调用pthread_cond_destroy函数进行销毁
int pthread_cond_destroy(pthread_cond_t *cond);
返回值:
	int ret = pthread_cond_destroy(&cond_d);  if (ret != 0) {  fprintf(stderr, "Failed to destroy condition variable: %s\n", strerror(ret));  exit(EXIT_FAILURE);  }

4.条件变量的等待

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:cond:要在这个条件变量上等待mutex:互斥量

5.唤醒(一般是其他线程中)条件变量的等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

http://www.ppmy.cn/ops/126985.html

相关文章

没有HTTPS 证书时,像这样实现多路复用

在没有 HTTPS 证书的情况下,HTTP/2 通常不能直接通过 HTTP 协议使用。虽然 HTTP/2 协议的规范是可以支持纯 HTTP 连接(即通过 http:// 协议),但大多数主流浏览器(如 Chrome、Firefox)都 强制要求 HTTP/2 必须在 HTTPS 上运行。这是出于安全和隐私的考虑。 因此,如果你没…

DNS代理是什么?浅析DNS代理的工作原理及应用

DNS代理作为计算机网络中重要的一环&#xff0c;扮演着连接用户和互联网服务的关键角色。来了解DNS代理的定义、功能、工作原理以及在网络中的应用场景和重要性吧。 一、理解DNS代理。 DNS代理充当在用户和真正的DNS服务器之间的中介。它接收来自用户端的DNS查询请求&#xf…

pytest中@pytest.fixture常用顺序function

ytest中pytest.fixture用法讲解 1、测试函数开始之前2、执行测试函数&#xff1a;3、测试函数结束后&#xff1a; 备注&#xff1a;内容来自chatGPT 在 pytest 中&#xff0c;pytest.fixture 是一个非常强大的功能&#xff0c;用于设置测试所需的环境和状态。它可以通过 scope…

SpringBoot框架下的汽车票在线预订系统

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

数据结构_day5

目录 7.树 7.1 特性 7.1.1 什么是树 7.1.2 关于树的一些术语 7.2 二叉树 7.2.1 什么是二叉树 7.2.2 二叉树性质&#xff08;重点&#xff09; 7.2.3 满二叉树和完全二叉树 7.2.4 二叉树的存储结构 7.3 二叉树的链式存储 7.4 层次遍历 哈夫曼树 Huffman 图 1.什么是图 2.图的基本…

【Hive】3-HiveSQL 数据定义语言(DDL)

HiveSQL 数据定义语言&#xff08;DDL&#xff09; SQL中DDL语法的作用 数据定义语言(Data Definition Language&#xff0c;DDL)&#xff0c;是SQL语言集中对数据库内部的对象结构进行创建&#xff0c;删除&#xff0c;修改等的操作语言&#xff0c;这些数据库对象包括datab…

C语言入门笔记:1.1 搭建开发环境

文章目录 一、C51与C251的区别二、安装Keil MDK三、C语言&#xff1a;菜鸟教程 一、C51与C251的区别 <1> 指令集数量不一样&#xff0c;C251有268条指令&#xff0c;C51有111条指令&#xff0c;前者可向下兼容后者的指令集&#xff0c;即Binary模式。 <2> 从指令种…

136-KC705E增强版基于FMC接口的 Kintex-7 XC7K325T PCIeX8 接口卡

一、板卡概述 本板卡基于Xilinx公司的FPGAXC7K325T-2FFG900 芯片&#xff0c;pin_to_pin兼容FPGAXC7K410T-2FFG900 &#xff0c;支持PCIeX8、64bit DDR3容量2GByte&#xff0c;HPC的FMC连接器&#xff0c;板卡支持各种接口输入&#xff0c;软件支持windows&#xff0c;Linux驱…