十七、多线程(下)

news2024/11/23 20:19:49

文章目录

  • 一、线程互斥,它是对的,但是不合理(饥饿问题)——同步
  • 二、条件变量
    • (一)概念
    • (二)条件变量接口
      • 1. pthread_cond_init 创建条件变量
      • 2. pthread_cond_wait 等待条件满足
      • 3. pthread_cond_destroy 销毁条件变量
      • 4. 唤醒等待
    • (三)例子
  • 三、生产者消费者模型
    • (一)321模型
    • (二)为何要使用生产者消费者模型
    • (三)生产者消费者模型优点
    • (四)基于BlockingQueue的生产者消费者模型
      • 1. 阻塞队列概念
      • 2. BlockingQueue阻塞队列代码
        • (1)pthread_cond_wait(&conCond_, &mutex_); 解锁
        • (2)生产者push时用while循环判断
  • 三、POSIX信号量
    • (一)概念
    • (二)信号量接口
      • 1. sem_init 初始化一个未命名的信号量
      • 2. sem_destroy 销毁一个信号量
      • 3. sem_wait 使用信号量(占座)
      • 4. sem_post 归还信号量(退座)
  • 三、环形队列
    • (一)信号量的作用
      • 1.数据为空:消费者不能超过生产者一>生产者先运行
      • 2.数据为满:生产者不能把消费者套一个圈然后继续再往后写入——消费者先运行
    • (二)code

一、线程互斥,它是对的,但是不合理(饥饿问题)——同步

不合理:互斥有可能导致饥饿问题——由于执行流1优先级高,她就不断的申请锁,释放锁,则另一个执行流2会长时间得不到某种资源。
在保证临界资源安全的前提下(互斥等),让线程访问某种资源,具有一定的顺序性,称之为同步

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

二、条件变量

(一)概念

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
条件(对应的共享资源的状态,程序员要判断资源是否满足自己操作的要求,为满/为空就不满足),条件变量(条件满足或者不满足的时候,进行wait或signal–种方式)。

条件变量:通过判断条件是否满足要求来决定是否让当前线程等待。

(二)条件变量接口

1. pthread_cond_init 创建条件变量

  • pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 定义全局/静态的条件变量,可以用这个宏初始化

  • int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 定义局部条件变量—— cond:条件变量的地址。attr:条件变量的属性设为空

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

2. pthread_cond_wait 等待条件满足

让对应的线程进行等待,等待被唤醒,即调用这个接口线程会被阻塞。
条件变量要和mutex互斥锁,一并使用,为什么?

条件变量的wait中需要传入锁的意义是:在阻塞线程的时候,会自动释放mutex_锁。

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

3. pthread_cond_destroy 销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond)

4. 唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒一个在指定条件变量下等待的所有线程
int pthread_cond_signal(pthread_cond_t *cond);  //唤醒一个在指定条件变量下等待的线程,一个一个唤醒时,所有线程以队列方式排列的

(三)例子

#include<vector>
#include<iostream>
#include<pthread.h>
#include<functional>

#include<unistd.h>
using namespace std;

pthread_cond_t cond;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

vector<function<void()>> funcs;

void show() {
    cout<<"hello show"<<" thread:"<<pthread_self()<<endl;
}
void print() {
    cout << "hello print" << endl;
}
void* waitCommand(void* args) {
    pthread_detach(pthread_self());
    //cout<<"!!!!!!!111111"<<endl;
    while(true)
    {
        pthread_cond_wait(&cond,&mutex);
        for(auto& f:funcs)
        {
            f();
        }
    }
    cout<<"thread id: "<<pthread_self()<<" end... "<<endl;
    return nullptr;

}
int main() {
    funcs.push_back(show);
    funcs.push_back(print);
    funcs.push_back([](){
        cout<<"你好,条件变量!"<<endl;
    });
    pthread_cond_init(&cond,nullptr);

    pthread_t t1,t2,t3;
    pthread_create(&t1,nullptr,waitCommand,nullptr);
    pthread_create(&t2,nullptr,waitCommand,nullptr);
    pthread_create(&t3,nullptr,waitCommand,nullptr);

    while(true) {
        sleep(1);
        pthread_cond_broadcast(&cond);
    }
    pthread_cond_destroy(&cond);

    return 0;
}

请添加图片描述

三、生产者消费者模型

生产者消费者模型——同步与互斥的最典型的应用场景——重新认识条件变量

(一)321模型

  • 生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者( 互斥/同步)——3种关系
  • 生产者和消费者——由线程承担的 2种角色
  • 超市:内存中特定的一种内存结构(数据结构)——1个交易场所
    在这里插入图片描述
    这里的仓库就是缓冲区,也是临界资源:①提高效率。②解耦生产者和消费者之间的耦合关系。
    (一般就是内存中的一段空间,可以有自己的组织方式)
  • 消费者有多个,消费者之间是竞争关系——互斥关系
  • 生产者有多个,生产者之间也是竞争关系——互斥关系
  • 消费者和生产者之间又是什么关系呢?
    1.互斥关系:生产者把"123456789"写入缓冲区时(正在生产),消费者突然来拿,可能只拿走了"12345"就错误了,所以消费者和生产者之间也要有互斥关系。
    2.同步关系:要有一定的顺序去执行——消费完了要生产,生产满了要消费

(二)为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

(三)生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

(四)基于BlockingQueue的生产者消费者模型

1. 阻塞队列概念

在多线程编程中阻塞队列 (Blocking Queue) 是一种常用于实现生产者和消费者模型的数据结构。
其与普通的队列区别在于:

  • 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;
  • 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出( 以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞),管道就是一种阻塞队列。
    在这里插入图片描述

2. BlockingQueue阻塞队列代码

(1)pthread_cond_wait(&conCond_, &mutex_); 解锁

挂起时生产者一定是在临界区中的,因为你要先上锁,不上锁就没有资格访问临界资源(如果不上锁就访问临界资源,则不符合同步的规则,那就是代码写的有问题)。此时上锁后被挂起,生产者和消费者用的同一把锁,如果 生产者/消费者 不释放锁(解锁)那么对方的线程就永远无法访问,所以条件变量的wait中需要传入锁的意义是:在阻塞线程的时候,会自动释放mutex_锁(解锁)

(2)生产者push时用while循环判断

while (isFull()):原因是 proBlockWait();调用的 pthread_cond_wait(&proCond_, &mutex_); 有可能返回失败,或被伪唤醒(伪唤醒可能是系统造成的或者写的代码有错误造成)。

如果是 if (isFull()) 则返回失败/伪唤醒后 就直接向下指向条件满足的代码,可是此时阻塞队列还是满的,再添加数据就错了;

所以用while (isFull()) 返回失败/伪唤醒后需要继续判断是否为满,为满就是返回失败/伪唤醒导致的—>重新等待;不满就是等待成功,消费者也消费了—>就可以添加数据了

三、POSIX信号量

一份公共资源,但是允许同时访问不同的区域!

不同的线程并发访问公共资源不同的区域!

(一)概念

POSIX信号量和 SystemV 信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的, 但 POSIX 可以用于线程间同步。

信号量:是一个计数器!
只要拥有信号量,就在未来一定能够拥有临界资源的一部分!

申请信号量的本质:对临界资源中特定小块资源的 <预定> 机制。

例子: 就和你在电影院买票一样,
你买了票,等于你预定了座位!!!
如果你没有买票,就进去坐着,这样是不符合规定的!

只要申请成功,就一定有你得资源!
只要申请失败,就说明条件不就绪,你只能等!

信号量的PV操作:V ++归还资源,P --申请资源。信号量的作用:限制进入临界区的线程个数。

线程要访问临界资源中的某一块区域 ——> 申请信号量 —— > 得先看到信号量 ——> 信号量本身必须是:公共资源

信号量的PV操作:
sem – ; 申请资源 —— 必须保证操作的原子性 p

sem ++ ; 归还资源 ——必须保证操作的原子性 v

(二)信号量接口

1. sem_init 初始化一个未命名的信号量

man 3 sem_init

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem:初始化的信号量。
pshared:0 表示线程间共享,非零表示进程间共享(填0)。
value :信号量初始值

2. sem_destroy 销毁一个信号量

int sem_destroy(sem_t *sem);

3. sem_wait 使用信号量(占座)

功能:等待信号量,会将信号量的值减1

int sem_wait(sem_t *sem); // P --;

4. sem_post 归还信号量(退座)

功能:发布信号量,表示资源使用完毕,可以归还资源了,将信号量值加1 。

int sem_post(sem_t *sem); // V ++

上面生产者 - 消费者的例子是基于 queue 的 , 其空间可以动态分配 , 现在基于固定大小的环形队列重写这个程序 (POSIX 信号量)。

三、环形队列

(一)信号量的作用

后续操作基本原则:(信号量保证满数据情况只能是消费线程先消费数据资源,数据为空的情况下生产线程先申请空间资源)

环形队列有可能访问同一个位置。什么时候会发生?

我们两个指向同一个位置的时候只有满or空的时候! ( 互斥and同步)其他时候,都指向不同的位置! (并发 )

1.数据为空:消费者不能超过生产者一>生产者先运行

生产者:最关心的是什么资源:空间默认是N: [N, 0]

2.数据为满:生产者不能把消费者套一个圈然后继续再往后写入——消费者先运行

消费者:最关心的是什么资源:数据默认是0 [0,N]

(二)code

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

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

相关文章

类的成员之:构造器(构造方法)

1.构造器的特征&#xff1a; 它具有与类相同的名称它不声明返回值类型。&#xff08;与声明为void不同&#xff09;不能被static、final、synchronized、abstract、native修饰&#xff0c;不能有return语句返回值 2.构造器的作用&#xff1a; 1.创建对象2.初始化对象的…

StackLLaMA: A hands-on guide to train LLaMA with RLHF

Paper name StackLLaMA: A hands-on guide to train LLaMA with RLHF Paper Reading Note Project URL: https://huggingface.co/blog/stackllama Code URL: https://huggingface.co/docs/trl/index TL;DR Huggingface 公司开发的 RLHF 训练代码&#xff0c;已集成到 hugg…

产品设计-产品设计五要素

概念介绍 产品设计五要素分别是&#xff1a;战略层、范围层、结构层、框架层、表现层。自上而下的分析可用来分析已有的产品&#xff0c;自下而上分析则可以用来创造新的产品。下面是各个层级所包括的内容&#xff1a; 战略层&#xff1a;产品目标和用户需求&#xff08;做什…

【STL(2)】

STL&#xff08;2&#xff09; 知识点回顾函数对象函数对象理解系统的仿函数仿函数应用 容器适配器stackdeque queuepriority_queue mapmap使用插入访问下标访问的应用&#xff1a;计算文件中单词的个数 知识点回顾 在STL库中存在三个容器适配器&#xff0c;stack - queue - p…

西门子200系列PLC学习课程大纲(课程筹备中)

西门子200系列PLC学习课程大纲如下表所示&#xff0c;共106课&#xff0c;关注我&#xff0c;让你从菜鸟变大神。 第1课西门子200PLC概述S7-200 PLC新特性是什么第2课S7-200 PLC的CPU介绍第3课S7-200 PLC编程软件介绍第4课S7-200 PLC通信方式有哪些第5课S7-200 PLC显示面板介绍…

6.1——我在CSDN的创作纪念日

文章目录 ⭐前言⭐相遇CSDN⭐切换到编程赛道的契机&#x1f496; 好好的美工为什么切换编程赛道&#x1f496; 转换编程赛道的催化剂 ⭐写博客的目的——写给未来的自己&#x1f496; 初衷——为学习铺路&#x1f496; 博客是灯——照亮前行的路&#x1f496; 博客是路——互联…

wenet-基于预训练模型进行增量训练

1867-154075-0014 重中之重 run.sh脚本分析 wenet aishell脚本解析_weixin_43870390的博客-CSDN博客 一、准备工作 第一步&#xff1a;准备训练数据&#xff0c;拷贝到远程服务器 将准备好的数据文件0529_0531_dataset&#xff0c;上传到恒源云上的/hy-tmp/wenet/example…

数据结构与算法10:递归树、Trie树、B+树

目录 【递归树】 【Trie 树】 【B树】 【每日一练&#xff1a;最长公共前缀】 【递归树】 递归的思想是将大问题分解为小问题&#xff0c;然后再将小问题分解为更小的问题&#xff0c;直到问题的数据规模被分解得足够小&#xff0c;不用继续递归分解为止。如果把这个一层…

Effective第三版 中英 | 第2章 创建和销毁对象 | 用私有构造器或者枚举类型强化 Singleton 属性

文章目录 Effective第三版前言第二章 创建和销毁对象用私有构造器或者枚举类型强化 Singleton 属性 Effective第三版 前言 大家好&#xff0c;这里是 Rocky 编程日记 &#xff0c;喜欢后端架构及中间件源码&#xff0c;目前正在阅读 effective-java 书籍。同时也把自己学习该书…

如何在本地配置Github的项目--Python

如何在本地配置Github的项目 0. 引言1. 初步预览2. 配置环境2.1 环境已经给出2.2 环境未曾给出 3. 数据配置4. 依次调试5. 配置完成总结 0. 引言 Github上存在大量的代码。当下载下来后可能会存在疑惑&#xff1a;如何在本地配置对应的项目呢&#xff1f; 为了帮助新手解决这一…

【Android开发基础】购物车代码整理

文章目录 一、数据库设计二、Home界面三、购物车模块四、添加五、源代码 这个月总算忙完了&#xff0c;总算能够抽出时间来&#xff0c;认真写一下博客了。整理一下购物车的代码 一、数据库设计 基于SqLite简单设计一个数据存储逻辑 实体&#xff08;接收数据&#xff09; im…

【数据加密】古典密码Playfair

文章目录 一、引言1、主要任务2、分支3、密码体制分类4、攻击密码系统 二、普莱费厄体制1、构造字母表&#xff0c;设为密钥矩阵2、设立加密方法3、加密解密4、字典集合5、结果 一、引言 1、主要任务 解决信息的保密性和可认证问题&#xff0c;保证信息在生成、传递、处理、保…

Swin-Transformer详解

Swin-Transformer详解 0. 前言1. Swin-Transformer结构简介2. Swin-Transformer结构详解2.1 Patch Partition2.2 Patch Merging2.3 Swin Transformer Block2.3.1 W-MSA2.3.2 SW-MSA 3. 模型配置总结 0. 前言 Swin-Transformer是2021年微软研究院发表在ICCV上的一篇文章&#x…

数据的存储(浮点型)

目录 浮点型存储的规则 1.前面我们已经学过了整形在数据中的存储是以原码&#xff0c;反码&#xff0c;补码的形式在内存中存储的&#xff0c;那么浮点数是以什么样的形式存储的呢&#xff1f; 接下来我们通过一段代码来观察——> int main() {int n 9;float* p (float*…

String AOP的使用

面向切面编程&#xff0c;面向特定方法编程&#xff0c;以方法为对象&#xff0c;在不修改原方法的基础上&#xff0c;对方法进行操作扩展等&#xff0c;底层是通过动态代理实现的 使用开发步骤&#xff1a; 1、创建一个类&#xff0c;加上Aspect声明为一个AOP切面类&#xff…

2023 重新开始

感觉搞 IT 的日子最近都有点不太好过。 早上接到公司电话说今天是一个大日子。 为什么是大日子&#xff0c;相信所有人都是懂的。这次公司将会经历一次非常大的裁员&#xff0c;很不幸也在列表中。不过感觉这个好像也没有什么关系。 因为早就在意料之中的事情&#xff0c;经历…

c语言之结构体(初阶)

目录 1&#xff1a;结构体类型的声明 2&#xff1a;结构体初始化 3&#xff1a;结构体成员访问 4&#xff1a;结构体传参 1&#xff1a;结构体类型的声明 1&#xff1a;为啥要有结构体&#xff0c;因为当我们描述一个复杂对象的时候&#xff0c;可能平时我们的一个类型不能…

常见的五种排序

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…

批量提取某音视频文案(二)

牙叔教程 简单易懂 之前写过一篇 批量提取某音视频文案 , 在之前的教程中, 我用的是微软的语音转文字功能, 今天我们换个方法, 使用 逗哥配音 的 文案提取 功能 准备工作 下载视频和音频 我在github找到的是这个仓库 https://github.com/Johnserf-Seed/TikTokDownload 注意一…

VLANIF虚接口案例实践

1&#xff09;拓扑 2&#xff09;需求&#xff1a; -所有PC能够ping通自己的网关 -实现vlan间互通&#xff0c;实现所有的PC互通 3&#xff09;配置步骤&#xff1a; 第一步&#xff1a;给pc配置IP地址 第二步&#xff1a;交换机创建vlan,做access和trunk -所有的交换机都配…