Linux下的多线程编程:原理、工具及应用(3)

                                               🎬慕斯主页:修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

条件变量再理解

pthread_cond_t

PTHREAD_COND_INITIALIZER

pthread_cond_init()

pthread_cond_destroy()

pthread_cond_wait()

pthread_cond_signal()

pthread_cond_broadcast()

示例代码

生产者消费者模型再理解

BlockingQueue 概念

BlockingQueue 的实现

示例代码

进一步封装


        从前面的理解中,我们对于死锁和条件变量有了一定程度的了解。对此,下面继续对于死锁和条件变量的共同作用加深理解。

条件变量再理解

pthread_cond_t

    pthread_cond_t是POSIX线程库中用于线程同步和通信的一种条件变量类型。它允许一个或多个线程等待直到其他线程通过信号通知特定条件已满足,从而实现线程间的协调工作。以下是关于pthread_cond_t的详细说明:

  1. 初始化:在开始使用pthread_cond_t之前,需要对其进行初始化。可以通过静态或动态的方式初始化条件变量。静态初始化通常在声明时直接赋予PTHREAD_COND_INITIALIZER值。如果选择动态初始化,则需要调用pthread_cond_init函数。
  2. 等待与唤醒机制:线程在等待某个条件变量时会进入睡眠状态,并释放其持有的互斥锁,这样其他线程可以执行相应的条件改变操作。当条件满足后,其他线程将通过pthread_cond_signalpthread_cond_broadcast函数唤醒等待该条件的线程。被唤醒的线程在返回前通常会再次获得互斥锁,以确保同步访问共享资源。
  3. 配合互斥锁使用:条件变量通常与互斥锁一起使用。线程在等待条件变量之前必须先锁定互斥锁,并在调用pthread_cond_wait之后解锁,以便其他线程可以访问共享资源并修改条件。在从pthread_cond_wait返回之前,线程会重新锁定互斥锁,以继续其工作。
  4. 销毁:当条件变量不再使用时,应调用pthread_cond_destroy函数进行清理,以避免资源泄露。
  5. 注意事项:在使用条件变量时要注意避免竞态条件和死锁,确保在检查条件和调用等待函数之间的操作是原子性的。
  6. 用途举例:条件变量常用于生产者-消费者问题、读写锁实现等多线程同步场景。

PTHREAD_COND_INITIALIZER

    PTHREAD_COND_INITIALIZER是POSIX线程库中用于初始化条件变量的宏。它的作用是将条件变量初始化为一个已定义的状态,以便在后续使用中进行比较和操作。

        具体来说,PTHREAD_COND_INITIALIZER是一个静态初始化器,可以在声明条件变量时直接将其赋值给条件变量。这个宏会将条件变量的内存设置为0,表示该条件变量尚未被初始化。

        以下是使用PTHREAD_COND_INITIALIZER进行条件变量初始化的示例代码:

#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

        在这个例子中,我们声明了一个名为cond的条件变量,并使用PTHREAD_COND_INITIALIZER将其初始化为0。这样,我们就可以在后续的代码中使用cond来进行线程同步和通信的操作了。

        需要注意的是,PTHREAD_COND_INITIALIZER只能用于静态初始化,不能用于动态初始化。如果需要在运行时动态创建条件变量,需要使用pthread_cond_init()函数进行初始化。

pthread_cond_init()

    pthread_cond_init()是POSIX线程库中的一个函数,用于初始化条件变量。条件变量是一种同步机制,允许线程等待某个条件满足后再继续执行。

        该函数的原型如下:

#include <pthread.h>int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

参数说明:

  • cond:指向要初始化的条件变量的指针。
  • attr:指向条件变量属性对象的指针,可以设置为NULL表示使用默认属性。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果使用了自定义的属性对象,也需要在适当的时候调用pthread_condattr_destroy()函数进行销毁。

pthread_cond_destroy()

    pthread_cond_destroy()是POSIX线程库中的一个函数,用于销毁条件变量

        该函数的原型如下:

#include <pthread.h>int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond:指向要销毁的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果条件变量正在被其他线程等待,则无法销毁该条件变量,直到所有等待该条件的线程已经返回。

pthread_cond_wait()

    pthread_cond_wait()是POSIX线程库中的一个函数,用于等待条件变量满足

        该函数的原型如下:

#include <pthread.h>int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数说明:

  • cond:指向要等待的条件变量的指针。
  • mutex:指向与条件变量关联的互斥锁的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_wait()函数会自动释放传入的互斥锁,并使当前线程进入阻塞状态,直到其他线程调用pthread_cond_signal()pthread_cond_broadcast()函数唤醒该线程。
  • 在调用pthread_cond_wait()函数之前,必须先加锁互斥锁,否则会导致未定义的行为。

pthread_cond_signal()

    pthread_cond_signal()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的一个线程。

        该函数的原型如下:

int pthread_cond_signal(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_signal()函数只会唤醒等待在条件变量上的一个线程,如果有多个线程在等待,其他线程将继续等待。
  • 如果当前没有线程在等待条件变量,pthread_cond_signal()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

pthread_cond_broadcast()

    pthread_cond_broadcast()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的所有线程。

        该函数的原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_broadcast()函数会唤醒等待在条件变量上的所有线程,而不仅仅是一个线程。如果有多个线程在等待,它们都将被唤醒并继续执行。
  • 如果当前没有线程在等待条件变量,pthread_cond_broadcast()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

示例代码

        使用互斥锁与条件等待来使得代码高效运行,以防某个线程一直占用锁从而占用资源!

#include <iostream> 
#include <unistd.h>
#include <pthread.h>
#include <string>pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;int tickets=1000;void *threadRoutine(void *args)
{std::string name=static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);if(tickets>0){std::cout << name<< ", get a ticket: " << tickets-- << std::endl; // 模拟抢票usleep(1000);}else {std::cout << "没有票了," << name << std::endl;// 1. 让线程在进行等待的时候,会自动释放锁 // 2. 线程被唤醒的时候,是在临界区内唤醒的,当线程被唤醒, 线程在pthread_cond_wait返回的时候,要重新申请并持有锁// 3. 当线程被唤醒的时候,重新申请并持有锁本质是也要参与锁的竞争的!!pthread_cond_wait(&cond,&mutex);}pthread_mutex_unlock(&mutex);}
}int main()
{//child pthreadpthread_t t1,t2,t3;pthread_create(&t1,nullptr,threadRoutine,(void*)"thread-1");pthread_create(&t2,nullptr,threadRoutine,(void*)"thread-2");pthread_create(&t3,nullptr,threadRoutine,(void*)"thread-3");//main pthreadwhile(true){sleep(5);pthread_mutex_lock(&mutex);tickets+=1000;pthread_mutex_unlock(&mutex);pthread_cond_broadcast(&cond);//给全部发信号//pthread_cond_signal(&cond);//给其中一个发信号}pthread_join(t1,nullptr);pthread_join(t1,nullptr);pthread_join(t1,nullptr);return 0;}

生产者消费者模型再理解

BlockingQueue 概念

        在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞) 大致图解:

BlockingQueue 的实现

        主要是基于还是基于库中queue来进行包装,push作为生产者的生产操作,而pop作为消费者的消费操作其中的细节:当queue到达我们设定的满队列值时,需要根据条件变量来等待,而发生这个信号在消费者的pop函数。相对的pop函数中如果队列为空了,那么也需要等待push函数中的信号

BlockQueue.hpp

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>const int defaultcap = 5;//首先默认队列大小为5template <class T>
class BlockQueue
{
public:BlockQueue(int cap = defaultcap): _capacity(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}bool IsFull(){return _q.size() == _capacity;}bool IsEmpty(){return _q.size() == 0;}void Push(const T &in) // 生产者生产{pthread_mutex_lock(&_mutex);//本来使用的是if,但是为了防止pthread_cond_wait伪唤醒从而使用whilewhile (IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性{// 阻塞等待pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!}_q.push(in);// if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);pthread_cond_signal(&_c_cond);pthread_mutex_unlock(&_mutex);}void Pop(T *out) // 消费者的{pthread_mutex_lock(&_mutex);while (IsEmpty()){// 阻塞等待pthread_cond_wait(&_c_cond, &_mutex);}*out = _q.front();_q.pop();//if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);pthread_cond_signal(&_p_cond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}private:std::queue<T> _q;int _capacity;pthread_mutex_t _mutex; // 锁pthread_cond_t _p_cond; // 生产者条件pthread_cond_t _c_cond; // 消费者的条件};

示例代码

        需要特别注意其中的sleep,如果在消费者函数或者生产者函数中表示为另外一方先行执行!但是根据上面我们push和pop函数的相互等待条件。如果是消费者先执行,那么他会等待生产者生产,每生产一个就消费一个。而如果生产者先执行,则会在一瞬间生产很多,而后消费者每消费一个,生产者生产一个。

#include "BlockQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>void *consumer(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while (true){int data;//sleep(1);bq->Pop(&data);std::cout << "consumer data: " << data << std::endl;}return nullptr;
}void *productor(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while (true){int data = rand() % 10;std::cout << "productor task: " << data << std::endl;bq->Push(data);sleep(1);}
}int main()
{srand((uint16_t)time(nullptr) ^ getpid() ^ pthread_self()); // 只是为了形成更随机的数据BlockQueue<int> *bq = new BlockQueue<int>();pthread_t c, p; // 消费者和生产者pthread_create(&c, nullptr, consumer, bq);pthread_create(&p, nullptr, productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}

进一步封装

        进一步封装,延续第一篇的代码,把锁也一同封装了!让代码更加简洁!

LockGuard.hpp

#pragma once#include <pthread.h>// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex _mutex;
};

BlockQueue.hpp

#pragma  once#include <iostream>
#include <queue>
#include <pthread.h>
#include "LockGuard.hpp"const int defaultcap = 5; // for testtemplate<class T>
class BlockQueue
{
public:BlockQueue(int cap = defaultcap):_capacity(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_p_cond, nullptr);pthread_cond_init(&_c_cond, nullptr);}bool IsFull(){return _q.size() == _capacity;}bool IsEmpty(){return _q.size() == 0;}void Push(const T &in) // 生产者的{LockGuard lockguard(&_mutex);// pthread_mutex_lock(&_mutex); // 2. lockguard 3. 重新理解生产消费模型(代码+理论) 4. 代码整体改成多生产,多消费while(IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性{// 阻塞等待pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!}_q.push(in);// if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);pthread_cond_signal(&_c_cond);// pthread_mutex_unlock(&_mutex);}void Pop(T *out)       // 消费者的{LockGuard lockguard(&_mutex);// pthread_mutex_lock(&_mutex);while(IsEmpty()){// 阻塞等待pthread_cond_wait(&_c_cond, &_mutex);}*out = _q.front();_q.pop();// if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);pthread_cond_signal(&_p_cond);// pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);}
private:std::queue<T> _q;int _capacity; // _q.size() == _capacity, 满了,不能在生产,_q.size() == 0, 空,不能消费了pthread_mutex_t _mutex;pthread_cond_t _p_cond; // 给生产者的pthread_cond_t _c_cond; // 给消费者的};


                      感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

基于VJ算法(Viola-Jones algorithm)的人面定位算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

【解决】Github Pages搭建的网页访问加载缓慢

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、CDN技术简介二、基于Cloudflare平台使用CDN服务&#xff08;一&#xff09;添加网站&#xff08;二&#xff09…

MechanicalSoup,一个非常实用的 Python 自动化浏览器交互工具库!

目录 前言 什么是 Python MechanicalSoup 库&#xff1f; 核心功能 使用方法 1. 安装 MechanicalSoup 库 2. 创建 MechanicalSoup 客户端 3. 打开网页并与之交互 实际应用场景 1. 网页自动化测试 2. 网络爬虫与数据提取 3. 网页自动化操作 4. 自动化填写和提交多个表单 5.…

facebook个人广告账户充值方式有哪些?看这一篇就够了

可以使用虚拟信用卡进行充值&#xff0c;也可以使用虚拟卡绑定paypal进行充值 点击获取虚拟卡 开卡步骤如下图 Facebook如何添加支付方式 1.前往支付设置。 2.在支付方式版块&#xff0c;点击添加支付方式。 3.选择要添加的支付方式&#xff0c;填写相关信息&#xff0c;然…

【JS】html字符转义

需求 将html转为字符串将html字符串转义&#xff0c;比如<div>转为<div> 码 /*** html标签字符转义* param {Stirng} str 要转换的html字符* returns String 返回转义的html字符串*/ const elToStr str > str.replaceAll(<, <).replaceAll(>, >)…

15届蓝桥杯第三期模拟赛所有题目解析

文章目录 &#x1f9e1;&#x1f9e1;t1_奇数次数&#x1f9e1;&#x1f9e1;思路代码 &#x1f9e1;&#x1f9e1;t2_台阶方案&#x1f9e1;&#x1f9e1;思路代码 &#x1f9e1;&#x1f9e1;t3_约数个数&#x1f9e1;&#x1f9e1;思路代码 &#x1f9e1;&#x1f9e1;t4_最…

详解MySQL的MVCC(ReadView部分解析C++源码)

文章目录 1. 什么是MVCC2. MVCC核心组成&#xff08;三大件&#xff09;2.1 MVCC为什么需要三大件 3. 隐藏字段4. undo log4.1 模拟版本链数据形成过程 5. Read View5.1 m_ids5.2 m_creator_trx_id5.3 m_low_limit_id5.4 m_up_limit_id5.5 可见性分析算法 6. MVCC流程模拟6.1 R…

flutter环境搭建实践

Dart Dart 是一种客户端和服务器端的编程语言&#xff0c;最早由 Google 提出。它被设计用于构建高性能、高度可伸缩和可靠的应用程序。Dart 可以编译成本地代码或者在虚拟机中直接运行。在移动应用开发中&#xff0c;Dart 主要用于开发 Flutter 应用。 Flutter 和 Dart 的关…

二叉平衡树和红黑树的代码实现(红黑树以后补充,目前代码也没怎么明白)

二叉平衡树 二叉平衡树节点定义 template<class K , class V> struct AVLTreeNode {AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf; //balance factorAVLTreeNode(const pair<K,…

ttkbootstrap界面美化系列之简介(一)

一&#xff1a;前言 相信很多同学用Python进行界面设计第一个用到的就是Tkinter&#xff0c;Tkinter是Python的一个标准接口&#xff0c;用于创建GUI&#xff08;图形用户界面&#xff09;应用程序。它是Tcl/Tk的封装&#xff0c;Tkinter的名称来源于Tk技术工具包(Tool…

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析&#xff1a;未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

亚马逊云科技Glue

Glue 最重要的部分&#xff0c; ETL&#xff1a;用于从 A 点&#xff08;我们的源数据&#xff09;提取、转换和加载数据到 B 点&#xff08;目标文件或数据存储库&#xff09;。 AWS Glue 会为您执行大量此类工作。 转换通常是更繁重的工作&#xff0c;需要从各种来源进行组合…

QML 添加扩展插件QQmlExtensionPlugin

一.添加QQmlExtensionPlugin方式步骤 目的&#xff1a;界面跨软件复用。 项目目录结构如下图&#xff1a; 1.首先&#xff0c;创建一个继承自QQmlExtensionPlugin的类&#xff0c;例如MyPlugin。在这个类中&#xff0c;实现registerTypes()和initializeEngine()方法。 #ifndef …

Transformer self-attention源码及原理理解

自注意力计算公式&#xff1a; 在公式(1)中Q(query)是输入一个序列中的一个token&#xff0c;K(key)代表序列中所有token的特征。 可以得到当前token与序列中其他token的相关性。在论文原文中512&#xff0c;表示每个token用512维特征表示&#xff08;序列符号的embedding长度…

子组件自定义事件$emit实现新页面弹窗关闭之后父界面刷新

文章目录 需求弹窗关闭之后父界面刷新展示最新数据 实现方案AVUE 大文本默认展开slotVUE 自定义事件实现 父界面刷新那么如何用呢? 思路核心代码1. 事件定义2. 帕斯卡命名组件且在父组件中引入以及注册3. 子组件被引用与父事件监听4.父组件回调函数 5.按钮弹窗事件 需求 弹窗…

【图像分割】使用Otsu 算法及迭代计算最佳全局阈值估计并实现图像分割(代码实现与分析)

本实验要求理解全局阈值分割的概念&#xff0c;并实现文本图像分割。需要大家深入理解Ostu 算法的实现过程及其迭代原理&#xff0c;同时通过学习使用Otsu 算法及其迭代&#xff0c;实践图像分割技术在文本图像处理中的应用。 以下将从实验原理、实验实现、实验结果分析三部分对…

短剧分销怎么赚钱的?保姆级教程助你短剧cps推广赚大钱

短剧分销怎么赚钱的&#xff1f;小白也能月入过万/“蜂小推“保姆级教程&#xff0c;助你短剧分销赚大钱&#xff01; 相信大家或多或少都在某些群里看到一些“霸道总裁爱上职场小菜鸟...”“这类链接&#xff0c;无利不起早&#xff0c;为什么会有那么多在群里分享这些狗血视…

紧抓需求,把脉市场,方太高端全场景厨电创造厨居新范式

撰稿 | 多客 来源 | 贝多财经 随着“中国制造”向“中国智造”方向转变&#xff0c;厨电不再是单一的工具设施&#xff0c;而是现代化厨居生活的映射&#xff0c;承担着沟通连接人、家庭与社会的桥梁作用。烹饪全场景下智能高效技术、整体美学设计、品类联动能力成为厨电品牌…

【机器学习系列】M3DM工业缺陷检测部署与训练

一.基础资料 1.Git 地址 地址 2.issues issues 3.参考 参考 csdn 二.服务器信息 1.GPU 服务器 GPU 服务器自带 CUDA 安装(前提是需要勾选上)CUDA 需要选择大于 11.3 的版本登录服务器后会自动安装 GPU 驱动 2.CUDA 安装 GPU 服务器自带 CUDA CUDA 版本查看 3.登录信…

从政府工作报告探计算机行业发展——探索计算机行业发展蓝图

目录 前言 一、政策导向与行业发展 &#xff08;一&#xff09;政策导向的影响 &#xff08;二&#xff09;企业如何把握政策机遇推动创新发展 二、技术创新与产业升级 三、数字经济与数字化转型 四、国际合作与竞争态势 五、行业人才培养与科技创新 &#xff08;一&a…