线程的同步

目录

引入

认识条件变量 

快速认识接口​编辑

认识条件变量​编辑

测试代码​编辑

生产消费模型

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

理解 

编写生产消费模型

BlockingQueue

单生产单消费 

多生产多消费 

引入

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步  

认识条件变量 

条件变量是C语言中使用POSIX线程(pthread)库实现线程同步的一种机制。它允许线程在某个条件满足之前进入等待状态,直到其他线程通知它们该条件已改变。

快速认识接口

认识条件变量

测试代码

一次唤醒一个:

一次全部唤醒: 

#include <iostream>
#include <string>
#include<unistd.h>
#include <pthread.h>
using namespace std;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//定义一把全局的锁
pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//全局条件变量
int num = 5;void* waitt(void*agv){string name=static_cast<const char*>(agv);while(true){pthread_mutex_lock(&gmutex);//加锁 pthread_cond_wait(&gcond,&gmutex);//条件变量等待,这里就是线程等待的位置usleep(10000);cout<<"i am: "<<name<<endl;pthread_mutex_unlock(&gmutex);//解锁}
}
int main()
{pthread_t threads[num];for (int i = 0; i < num; i++)//创建线程{char *name = new char[1024];              // 用来动态分配一个大小为1024字节的字符数组,并将其地址赋给指针namesnprintf(name, 1024, "thread-%d", i + 1); // 注意不用sizeof了因为sizeof(name)为地址字节不是大小了pthread_create(threads + i, nullptr, waitt, (void *)name);usleep(10000);}sleep(1);while(true){//唤醒pthread_cond_broadcast(&gcond);//全部唤醒//pthread_cond_signal(&gcond);//一次一个cout<<"唤醒一个线程......"<<endl;sleep(2);//唤醒的慢些}for (int i = 0; i < num; i++)//线程等待{           pthread_join(threads[i],nullptr);}
}

生产消费模型

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

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

理解 

生产消费模型通常是多执行流并发的模型------多个执行流之间怎么进行互斥同步之间如何协同的模型;

供应商和消费者就是线程,超市就是一段内存,方便面就是数据;

未来生产线程将数据放到缓存中,消费者需要的时候从缓存拿;

 思考切入点:“321”原则

                     1.  一个交易场所(特定数据结构形式存在的一段内存空间)

                     2.  两种角色(生产角色,消费角色)--生产线程,消费线程

                     3.  3种关系(生产和生产(竞争关系-互斥),消费和消费(互斥--资源少时就竞争了),生产和消费(互斥--生产者在向缓冲区写入数据时,消费者不能同时从缓冲区中读取数据,以免读取错误数据&&同步--消费者消费数据导致没数据就通知生产者放数据))

实现生产消费模型本质就是:通过代码实现321原则,用锁和条件变量(或其他方式)来实现三种关系!

编写生产消费模型

BlockingQueue

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

那么可以想到进程间通信时用的管道,一个向管道里写另一个从管道里读。管道如果满了,写进程就要阻塞,如果空了,读进程就要阻塞。管道就是典型的阻塞队列--生产消费模型,只不过使用进程来代替;

单生产单消费 

那么生产和生产以及消费和消费之间的关系就不用维护了;

简单测试代码

blockqueue.hpp:

#pragma once
#include <iostream>
#include <string>
#include <queue> //使用stl的queue
#include <pthread.h>using namespace std;
const static int defaultcap = 5;template <typename T> // 引入模板
class blockqueue
{
private:bool full(){return _block_queue.size() == _max_cap;}bool empty(){return _block_queue.empty();}public:blockqueue(int cap = defaultcap) : _max_cap(cap){pthread_mutex_init(&_mutex, nullptr); // 初始化锁pthread_cond_init(&_p_cond, nullptr); // 初始条件变量pthread_cond_init(&_c_cond, nullptr); // 初始条件变量}void Pop(T *out){                                // 把队列中的数据带出去pthread_mutex_lock(&_mutex); // 用的同一把锁,互斥关系while (empty()){pthread_cond_wait(&_c_cond, &_mutex); // 在该条件变量下等待}// 此时走到这里,1.没有满 || 2.被唤醒了*out = _block_queue.front();_block_queue.pop(); // 拿出数据了pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 消费一个了那么你就可以生产了,队列有空间了// signal放在unlock之前还是之后都是可以的}// 那么唤醒可以由他们两个互相唤醒互相void equeue(const T &in){                                // 入队列pthread_mutex_lock(&_mutex); // 加锁防止向临界区放数据被打扰while (full()){ // 判断是否为满了// 满了不能生产,必须等待// 此时在临界区里,加锁和解锁之间// pthread_cond_wait在被调用的时候:除了让自己继续排队等待,还会自己释放传入的锁// 函数返回的时候,不就还在临界区么?那么被返回时必须先参与锁的竞争,重新加上锁,该函数才被返回;那么返回时就有锁了pthread_cond_wait(&_p_cond, &_mutex); // 在该条件变量下等待}// 此时走到这里,1.没有满 || 2.被唤醒了_block_queue.push(in); // 生产到阻塞队列里,此时没解锁所以一定至少有一个数据在队列里pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_c_cond); // 生产一个了那么你就可以消费了// 让消费者消费}~blockqueue(){pthread_mutex_destroy(&_mutex); // 将锁销毁pthread_cond_destroy(&_p_cond); // 将局部条件变量销毁pthread_cond_destroy(&_c_cond); // 将局部条件变量销毁}private:queue<T> _block_queue;  // 临界资源int _max_cap;           // 队列最大容量pthread_mutex_t _mutex; // 锁pthread_cond_t _p_cond; // 为生产者提供的条件变量pthread_cond_t _c_cond; // 为消费者提供的条件变量
};

 main.cc:

构建数据(int)

#include"blockqueue.hpp"
#include<pthread.h>
#include<ctime>
#include<unistd.h>void*consumer(void*agv){blockqueue<int>* bq=static_cast<blockqueue<int>* >(agv);while(true){sleep(2);//消费的慢些//1.拿数据int date=0;bq->Pop(&date);//拿//2.处理数据cout<<"consumer-> "<<date<<endl;}
}
void*productor(void*agv){srand(time(nullptr)^getpid());//增加随机性blockqueue<int>* bq=static_cast<blockqueue<int>* >(agv);//两个线程看到同一个阻塞队列while(true){//1.构建数据int date=rand()%10+1;//[1,10]//2.生产数据bq->equeue(date);//入cout<<"producter-> "<<date<<endl;}
}int main(){blockqueue<int>* bq=new blockqueue<int>();//使用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;
}// #include <iostream>
// #include <string>
// #include<unistd.h>
// #include <pthread.h>
// using namespace std;
// pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//定义一把全局的锁
// pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//全局条件变量
// int num = 5;// void* waitt(void*agv){
//     string name=static_cast<const char*>(agv);
//     while(true){
//         pthread_mutex_lock(&gmutex);//加锁 
//         pthread_cond_wait(&gcond,&gmutex);//条件变量等待,这里就是线程等待的位置
//             usleep(10000);
//     cout<<"i am: "<<name<<endl;
//     pthread_mutex_unlock(&gmutex);//解锁
//     }
// }
// int main()
// {
//     pthread_t threads[num];
//     for (int i = 0; i < num; i++)//创建线程
//     {
//         char *name = new char[1024];              // 用来动态分配一个大小为1024字节的字符数组,并将其地址赋给指针name
//         snprintf(name, 1024, "thread-%d", i + 1); // 注意不用sizeof了因为sizeof(name)为地址字节不是大小了
//         pthread_create(threads + i, nullptr, waitt, (void *)name);
//         usleep(10000);
//     }
//     sleep(1);
//     while(true){//唤醒
//         pthread_cond_broadcast(&gcond);//全部唤醒
//         //pthread_cond_signal(&gcond);//一次一个
//         cout<<"唤醒一个线程......"<<endl;
//         sleep(2);//唤醒的慢些
//     }//     for (int i = 0; i < num; i++)//线程等待
//     {           
//         pthread_join(threads[i],nullptr);
//     }
// }

main.cc: 

构建任务(task--类)

#include"blockqueue.hpp"
#include"task.hpp"
#include<pthread.h>
#include<ctime>
#include<unistd.h>void*consumer(void*agv){blockqueue<task>* bq=static_cast<blockqueue<task>* >(agv);while(true){sleep(2);//消费的慢些//1.拿数据task t;//无参构造bq->Pop(&t);//拿//2.处理数据t.excute();cout<<"consumer-> "<<t.result()<<endl;}
}
void*productor(void*agv){srand(time(nullptr)^getpid());//增加随机性blockqueue<task>* bq=static_cast<blockqueue<task>* >(agv);//两个线程看到同一个阻塞队列while(true){//1.构建数据int x=rand()%10+1;//[1,10]usleep(10000);//让两个数据尽量不一样,休眠一段时间int y=rand()%10+1;//[1,10]//2.生产数据task t(x,y);//有参构造bq->equeue(t);//入cout<<"producter-> "<<t.debug()<<endl;}
}int main(){blockqueue<task>* bq=new blockqueue<task>();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;
}

task.hpp:

#pragma once
#include "blockqueue.hpp"
using namespace std;
class task
{
public:task() {}task(int x, int y) : _x(x), _y(y){}void excute(){_result = _x + _y;}string debug(){string msg = to_string(_x) + "+" + to_string(_y) + "=?";return msg;}string result(){string msg = to_string(_x) + "+" + to_string(_y) +"="+ to_string(_result);return msg;}~task(){}private:int _x;int _y;int _result;
};

main.cc:

仿函数

#include "blockqueue.hpp"
#include "task.hpp"
#include <pthread.h>
#include <ctime>
#include <unistd.h>void *consumer(void *agv)
{blockqueue<task_t> *bq = static_cast<blockqueue<task_t> *>(agv);while (true){sleep(2); task_t t;bq->Pop(&t);// 从队列中取出并执行任务t();// 执行任务}
}
void *productor(void *agv)
{srand(time(nullptr) ^ getpid());                             // 增加随机性blockqueue<task_t> *bq = static_cast<blockqueue<task_t> *>(agv); // 两个线程看到同一个阻塞队列while (true){bq->equeue(download);   cout<<"productor->download "<<endl;}
}int main()
{blockqueue<task_t> *bq = new blockqueue<task_t>();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;
}

task.hpp:

#pragma once
#include<iostream>
#include<functional>
using namespace std;using task_t=function<void()>;//等价于typedef function<void()> task_t;
//定义了一个新的类型别名 task_t,它表示一个接受无参数并返回 void 的函数类型void download(){cout<<"i am a download task"<<endl;
}

多生产多消费 

多生产多消费直接复用上面代码即可;

那么针对生产者不仅仅要将任务放到超市(花费时间),他还要产生任务也要花费时间;

那么针对消费者不仅仅要从超市拿到任务(花费时间),这个任务属于消费者自己了,那么他还要处理任务也要花费时间;

那么我们就不能仅仅只考虑放任务到超市叫生产,拿任务叫消费;

 那么一个生产商再放任务的时候,那么其他生产商有没有正在生产任务呢。

那么放任务和产生任务就并发了;

 那么一个消费者再拿任务的时候,那么其他消费者有没有早都获取了任务正在处理任务呢。

那么拿任务和处理任务就并发了;

如果未来消费处理任务花费时间比较久但是生产任务比较快,那么可以单生产多消费,那么生产任务的时候,一方面有线程在获取另一方面在并发处理任务;

如果未来生产任务花费时间比较久但是消费任务比较快,那么可以多生产单消费;

都很慢就可以多生产多消费;

问题:为什么等待就要在加锁和解锁之间等待呢?

 无论是生产者还是消费者都必须先检测资源的状态,对于生产和消费来说他们要访问公共资源,它不知道资源的条件是否满足。对于生产者来说希望队列有空间,对于消费者来说希望队列有数据。可是对于他们来说他们并不知道,只有他们查一次才知道,而查这一行为本身就是访问,就决定了查之前就要加锁,并且检测可能满足可能不满足,注定了必须在临界区里等待因为判定结果是在临界区里的;

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

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

相关文章

Hugging Face HUGS 加快了基于开放模型的AI应用的开发

在过去一年左右的时间里&#xff0c;开源人工智能模型在性能上已经明显赶上了 OpenAI、Google 和其他公司的流行闭源模型。 然而&#xff0c;由于在不同硬件上部署和维护这些模型所带来的开销&#xff0c;开发人员尚未广泛采用这些模型。为了解决这个问题&#xff0c;Hugging F…

【Unity】Unity中文本中插入超链接且可点击响应,TextMeshPro的进阶用法

一、需求和尝试 今天遇到这样一个需求&#xff1a;在文本中插入超链接&#xff0c;且这个链接可以点击跳转对应的url&#xff0c;具体形式如下图所示。 其实这个有一个简单粗暴的方法&#xff0c;就是把需要加超链接的文本单独拿出来&#xff0c;和其他文本进行拼接&#xf…

修改huggingface的缓存目录以及镜像源

执行以下语句查看当前配置 huggingface-cli env默认输出应该如下 (py39-transformers) PS D:\py_project\transformers_demo> huggingface-cli envCopy-and-paste the text below in your GitHub issue.- huggingface_hub version: 0.26.1 - Platform: Windows-10-10.0.22…

面向垂类场景的智能化低代码引擎

背景介绍 在通信领域中&#xff0c;不同客户的数字化场景存在多种个性化大屏的定制需求&#xff0c;常见业务范围涵盖政务、金融、教育、工业、传媒、互联网等行业领域。然而&#xff0c;面对如此巨大的产业痛点诉求&#xff0c;传统低代码领域却存在数据监控粒度不统一、定制化…

学习docker第三弹------Docker镜像以及推送拉取镜像到阿里云公有仓库和私有仓库

docker目录 1 Docker镜像dockers镜像的进一步理解 2 Docker镜像commit操作实例案例内容是ubuntu安装vim 3 将本地镜像推送至阿里云4 将阿里云镜像下载到本地仓库5 后记 1 Docker镜像 镜像&#xff0c;是docker的三件套之一&#xff08;镜像、容器、仓库&#xff09;&#xff0…

基于SpringBoot+Vue+MySQL的实践性教学系统

系统展示 用户前台界面 后台界面 系统背景 随着信息技术的快速发展&#xff0c;企业对于高效、智能的管理系统需求日益迫切。传统的管理系统大多采用单机版或C/S架构&#xff0c;存在操作复杂、维护困难、数据共享性差等问题。而基于SpringBootVueMySQL的全栈管理系统&#xff…

【10分钟本地部署大语言模型】借助1Panel、MaxKb、Ollama快速部署大语言模型(qwen、llama等)及知识库

前言&#xff1a; 本文介绍一种快速在Linux服务器&#xff08;windows使用wsl也可以&#xff09;上部署大语言模型&#xff08;含知识库&#xff09;的方式。 核心内容如下&#xff1a; 1Panel&#xff1a; 开源的Linux 服务器运维管理面板&#xff0c;通过该面板安装ollama和…

Serv00 免费虚拟主机 零成本搭建 PHP / Node.js 网站

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 Serv00 是一个提供免费虚拟主机的平台&#xff0c;包含了 3GB 的存储空间和 512MB 的内存空间&#xff0c;足够我们搭建一个 1IP 的小网站了。同时他还不限制每月的流量&#xff0c;并提供了 16 个数据库&…

进程间通信(一)管道

文章目录 进程间通信进程间通信概述进程间通信的方式管道通信示例--基于管道的父子进程通信示例--使用管道进程兄弟进程通信 管道的读写特性示例--不完整管道&#xff08;读一个写端关闭的管道&#xff09;示例--不完整管道&#xff08;写一个读端关闭的管道&#xff09; 标准库…

PyQt 入门教程(3)基础知识 | 3.1、使用QtDesigner创建.ui文件

文章目录 一、使用QtDesigner创建.ui文件1、创建.ui文件2、生成.py文件3、使用新生成的.py文件4、编辑新生成的.py文件 一、使用QtDesigner创建.ui文件 1、创建.ui文件 打开PyCharm&#xff0c;使用自定义外部工具QtDesigner创建mydialog.ui文件&#xff0c;如下&#xff1a; …

大话网络协议:HTTPS协议和HTTP协议有何不同?为什么HTTPS更安全

大家现在访问网络,浏览网页,注意一下的话,网址前面基本上都是一个 https:// 的前缀,这里就是说明这个网址所采用的协议是 https 协议。那么具体应该怎么理解 https 呢? 本文我们就力争能清楚地解释明白这个我们目前应该最广的协议。 理解HTTP协议 要解释 https 协议,当…

[mysql]聚合函数GROUP BY和HAVING的使用和sql查询语句的底层执行逻辑

#GROUP BY的使用 还是先从需求出发,我们现在想求员工表里各个部门的平均工资,最高工资 SELECT department_id,AVG(salary) FROM employees GROUP BY department_id 我们就会知道它会把一样的id分组,没有部门的就会分为一组,我们也可以用其他字段来分组,我们想查询不同jb_id…

动力电池中的基础知识总结

动力电池基础 基本定义 电池的分类方式多样&#xff0c;按工作特性和储存方式分类 一次电池&#xff08;Primary Battery&#xff09;&#xff1a;只能进行一次放电&#xff08;disposable or single-use batteries&#xff09;&#xff0c;放电后不能通过充电的方式使其恢复…

Postgresql pgsql 插件之postgis 安装配置

相关链接&#xff1a; pgsql编译安装 一、说明 postgis是pgsql最强大的几个插件之一&#xff0c;可以用于地理信息系统&#xff08;gis&#xff09;的搭建 二、插件安装启动 由于我的pgsql是编译安装的&#xff0c;所以插件也是编译安装&#xff0c;更加灵活。 1.进入到源…

草地杂草数据集野外草地数据集田间野草数据集YOLO格式VOC格式目标检测计算机视觉数据集

一、数据集概述 数据集名称&#xff1a;杂草图像数据集 数据集是一个包含野草种类的集合&#xff0c;其中每种野草都有详细的特征描述和标记。这些数据可以包括野草的图片、生长习性、叶片形状、颜色等特征。 1.1可能应用的领域 农业领域: 农业专家和农民可以利用这一数据集来…

IDEA无法生成自动化序列serialVersionUID及无法访问8080端口异常的解决方案

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;IDEA 今天是1024程序员节&#xff0c;先祝大家节日快乐&#xff01; 无法生成自动化序列serialVersionUID 如果我们在idea当中想要通过generate来生成自动化序列&#xff0c;如下图…

Nest.js 实战 (十五):前后端分离项目部署的最佳实践

☘️ 前言 本项目是一个采用现代前端框架 Vue3 与后端 Node.js 框架 Nest.js 实现的前后端分离架构的应用。Vue3 提供了高性能的前端组件化解决方案&#xff0c;而 Nest.js 则利用 TypeScript 带来的类型安全和模块化优势构建了一个健壮的服务端应用。通过这种技术栈组合&…

智慧升级,知识无界:十大搭建知识库软件助你前行

在知识爆炸的时代&#xff0c;如何高效地管理、整合与利用信息&#xff0c;成为了个人与企业发展的核心竞争力。智慧升级&#xff0c;意味着我们不仅要掌握丰富的知识&#xff0c;更要学会运用工具&#xff0c;让知识无界流通&#xff0c;助力个人成长与企业创新。以下是精心挑…

全网最全开放式自动猫砂盆测评!魔铲、cewey、萌娃有什么区别?

最近我发现很多铲屎官在购买开放式自动猫砂盆时&#xff0c;总是会在cewey、魔铲、萌娃之间犹豫&#xff0c;不知道这三款自动猫砂盆到底有什么不同&#xff0c;盲选又怕选错&#xff0c;买了个祖宗回去&#xff0c;今天我就给大家好好说说&#xff0c;cewey、魔铲、萌娃之间&a…

SL3160 dcdc150V降压5.1V/1A 车载GPS定位器供电芯片

一、主要特性 宽输入电压范围&#xff1a;SL3160支持10~150V的宽输入电压范围&#xff0c;使其能够适应各种电源电压波动&#xff0c;确保稳定输出。 高效降压转换&#xff1a;该芯片采用先进的电源管理技术&#xff0c;转换效率高达90%以上&#xff0c;降低了散热压力和整体…