线程的同步
目录
引入
认识条件变量
快速认识接口编辑
认识条件变量编辑
测试代码编辑
生产消费模型
为何要使用生产者消费者模型
理解
编写生产消费模型
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; }
多生产多消费
多生产多消费直接复用上面代码即可;
那么针对生产者不仅仅要将任务放到超市(花费时间),他还要产生任务也要花费时间;
那么针对消费者不仅仅要从超市拿到任务(花费时间),这个任务属于消费者自己了,那么他还要处理任务也要花费时间;
那么我们就不能仅仅只考虑放任务到超市叫生产,拿任务叫消费;
那么一个生产商再放任务的时候,那么其他生产商有没有正在生产任务呢。
那么放任务和产生任务就并发了;
那么一个消费者再拿任务的时候,那么其他消费者有没有早都获取了任务正在处理任务呢。
那么拿任务和处理任务就并发了;
如果未来消费处理任务花费时间比较久但是生产任务比较快,那么可以单生产多消费,那么生产任务的时候,一方面有线程在获取另一方面在并发处理任务;
如果未来生产任务花费时间比较久但是消费任务比较快,那么可以多生产单消费;
都很慢就可以多生产多消费;
问题:为什么等待就要在加锁和解锁之间等待呢?
无论是生产者还是消费者都必须先检测资源的状态,对于生产和消费来说他们要访问公共资源,它不知道资源的条件是否满足。对于生产者来说希望队列有空间,对于消费者来说希望队列有数据。可是对于他们来说他们并不知道,只有他们查一次才知道,而查这一行为本身就是访问,就决定了查之前就要加锁,并且检测可能满足可能不满足,注定了必须在临界区里等待因为判定结果是在临界区里的;
相关文章:
线程的同步
目录 引入 认识条件变量 快速认识接口编辑 认识条件变量编辑 测试代码编辑 生产消费模型 为何要使用生产者消费者模型 理解 编写生产消费模型 BlockingQueue 单生产单消费 多生产多消费 引入 同步:在保证数据安全的前提下,让线程…...
【启明智显分享】ZX7981PG/ZX7981PM融入官方OpenWrt,启明智显SDK/官方OpenWrt任由选择!
好消息!好消息!启明智显ZX7981PG和ZX7981PM正式融入官方 OpenWrt 的大家庭啦!现在开发者不仅可以基于启明智显的SDK进行二次开发,还可以直接应用官方OpenWrt以及我们的开源资料进行开发! 借助OpenWrt的强大生态&…...
如何用java发送包含表格形式的邮件
问题: 如何用java发送包含表格形式的邮件? 方法: 发用freemaker工具来替换html的表格变量,从而动态生成了html。然后再发送这个html格式(不能用纯文本)文本即可。 优化流程: 1、准备模板&#x…...
讲个故事:关于一次接口性能优化的心里路程
这是一个程序猿写的第一个故事,请各位懂行的客官静下心来,慢慢品读。就知道我为什么要单独写一个文章来记录这次过程了,因为实在是太坎坷了...... 背景介绍 近期项目投产时遇到一个问题,投产后在验证时发现大部分用户系统登…...
Centos7升级到openssh9.9
openssh9.9 是2024.9.20出的最新版ssh。因为客户扫描出一大堆centos7的漏洞,全是这个openssh的,好多补丁,所以索性升级到最新版。 需要自己制作rpm包,这个我是不懂,照这个来: Linux服务器升级openssh9.9最…...
使用 STM32F407 串口实现 485 通信
准备工作 了解485通信基本概念与原理:RS485通信详解_485通讯de接什么口-CSDN博客 安装编译软件:keil uVision 5.6 软件资料:STM32CubeF4 固件包,正点原子RS485通信例程 参考视频:第26讲 基础篇-新建H…...
基于NERF技术重建学习笔记
NeRF(Neural Radiance Fields)是一种用于3D场景重建的神经网络模型,能够从2D图像生成逼真的3D渲染效果。它将场景表征为一个连续的5D函数,利用了体积渲染和神经网络的结合,通过学习光线穿过空间时的颜色和密度来重建场…...
webView 支持全屏播放
webView 支持全屏播放 直接上代码 public class CustomFullScreenWebViewClient extends WebChromeClient {WebView webView;Context context;/*** 视频全屏参数*/protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS new FrameLayout.LayoutParams(ViewG…...
QGIS之三十二DEM地形导出三维模型gltf
效果 1、准备数据 (1)dem.tif (2)dom.tif 2、qgis加载dem和dom数据 3、安装插件 插件步骤可以参考这篇文章 QGIS之二十四安装插件 安装了Qgis2threejs插件,结果...
【python爬虫】携程旅行景点游客数据分析与可视化
一.选题背景 随着旅游业的快速发展,越来越多的人选择通过互联网平台预订旅行产品,其中携程网作为国内领先的在线旅行服务提供商,拥有大量的旅游产品和用户数据。利用爬虫技术可以获取携程网上各个景点的游客数据,包括游客数量、游…...
python实现onvif协议下控制摄像头变焦,以及融合人形识别与跟踪控制
#1024程序员节 | 征文# 这两天才因为项目需要,对网络摄像头的视频采集以及实现人形识别与跟踪技术。对于onvif协议自然起先也没有任何的了解。但是购买的摄像头是SONY网络头是用在其他地方的。因为前期支持探究项目解决方案,就直接拿来做demo测试使用。 …...
【Vue】Vue3.0(十四)接口,泛型和自定义类型的概念及使用
上篇文章: 【Vue】Vue3.0(十三)中标签属性ref(加在普通标签上、加在组件标签上)、局部样式 🏡作者主页:点击! 🤖Vue专栏:点击! ⏰️创作时间&…...
【C++】红黑树万字详解(一文彻底搞懂红黑树的底层逻辑)
目录 00.引入 01.红黑树的性质 02.红黑树的定义 03.红黑树的插入 1.按照二叉搜索树的规则插入新节点 2.检测新节点插入后,是否满足红黑树的性质 1.uncle节点存在且为红色 2.uncle节点不存在 3.uncle节点存在且为黑色 04.验证红黑树 00.引入 和AVL树一样&am…...
开源FluentFTP实操,操控FTP文件
概述:通过FluentFTP库,轻松在.NET中实现FTP功能。支持判断、创建、删除文件夹,判断文件是否存在,实现上传、下载和删除文件。简便而强大的FTP操作,提升文件传输效率。 在.NET中,使用FluentFTP库可以方便地…...
论文解读 | ECCV2024 AutoEval-Video:一个用于评估大型视觉-语言模型在开放式视频问答中的自动基准测试...
点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入! 点击 阅读原文 观看作者讲解回放! 作者简介 陈修元,上海交通大学清源研究院硕士生 概述 总结来说,我们提出了一个新颖且具有挑战性的基准测试AutoEvalVideo,用于全…...
postgresql14主从同步流复制搭建
1. 如果使用docker搭建请移步 Docker 启动 PostgreSQL 主从架构:实现数据同步的高效部署指南_docker安装postgresql主从同步-CSDN博客 2. 背景 pgsql版本:PostgreSQL 14.13 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4…...
企业信息化管理中的数据集成方案:销售出库单对接
企业信息化管理中的数据集成方案:销售出库单对接 销售出库单旺店通→金蝶:高效数据集成案例分享 在企业信息化管理中,数据的高效流动和准确对接是实现业务流程自动化的关键。本文将聚焦于一个具体的系统对接集成案例:如何将旺店通…...
3.cpp基本数据类型
cpp基本数据类型 1.cpp基本数据类型 1.cpp基本数据类型 C基本数据类型和C语言的基本数据类型差不多 注意bool类型:存储真值 true 或假值 false,C语言编译器C99以上支持。 C语言的bool类型:要添加 #include <stdbool.h>头文件 #includ…...
MCK主机加固与防漏扫的深度解析
在当今这个信息化飞速发展的时代,网络安全成为了企业不可忽视的重要议题。漏洞扫描,简称漏扫,是一种旨在发现计算机系统、网络或应用程序中潜在安全漏洞的技术手段。通过自动化工具,漏扫能够识别出系统中存在的已知漏洞࿰…...
《软件估算之原始功能点:精准度量软件规模的关键》
《软件估算之原始功能点:精准度量软件规模的关键》 一、软件估算的重要性与方法概述二、原始功能点的构成要素(一)数据功能(二)事务功能 三、原始功能点的估算方法(一)功能点分类估算࿰…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...






