当前位置: 首页 > news >正文

【Linux系统编程】第四十六弹---线程同步与生产消费模型深度解析

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、Linux线程同步

1.1、同步概念与竞态条件 

1.2、条件变量

1.2.1、认识条件变量接口

1.2.2、举例子认识条件变量

1.2.3、测试代码 

2、生产消费模型 

2.1、为何要使用生产消费模型

2.2、生产者消费者模型优点

2.3、编写生产消费模型

2.3.1、BlockQueue类基本结构

2.3.2、构造析构函数

2.3.3、判空判满函数

2.3.4、生产者入队

2.3.5、消费者出队

2.4、测试生产消费模型

2.4.1、内置类型

2.4.2、类类型

2.4.3、函数类型

2.4.4、多生产多消费 


1、Linux线程同步

在上一弹的上锁抢票代码中我们可以看到,会有很长一段时间使用的是同一个线程,这样的方式没有错,但是不合理,怎么解决这个问题呢?

 先通过一个实际情况分析此问题,再解决该问题。

假设学校有一个VIP自习室,一次只允许一个人进来,进入自习室需要用到门口的一把锁。

  • 有一个uu今天想去里面自习,就早早5点起床去了VIP自习室,但是他又想,竟然来了就多学习一会,此时外面也有人想进来自习,但是没有钥匙只能在外面等
  • 此时这个uu已经学了一上午了,很饿了,想去吃饭,走到门口,刚放回钥匙,又后悔了,如果现在还钥匙了,后面就不能进自习室了,因此这个uu又拿了钥匙进入了自习室(因为uu离钥匙比较近,因此还是他先拿到钥匙)

结论:其他人长时间无法进入自习室 --- 无法获取临界资源 -- 导致饥饿问题!!! 

因此我们可以修改规则,让进入自习室更公平!

每一个同学归还钥匙后:

1、不能立马申请

2、第二次申请,必须排队(换句话说,其他人也得排队)

1.1、同步概念与竞态条件 

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

1.2、条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

1.2.1、认识条件变量接口

初始化

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 全局或者静态只需初始化参数:cond:要初始化的条件变量attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

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

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所以线程
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒一个线程

1.2.2、举例子认识条件变量

假设有两个人,一个盘子,一个人放苹果到盘子里,另一个人从盘子里取苹果(前提是有苹果,因此需要先检查是否有苹果),但是互相都不知道什么时候放和取苹果,因此只能一次次的去尝试,是够放好,是否被取,但是这样会导致一个问题,如果一个人不放,那么另一个会一直去检查盘子里有没有苹果,这样就太浪费(线程)资源了,我们可以改进一下策略!!!

优化

我们可以再加一个铃铛,当取苹果的时候,如果盘子里面还没有苹果,那么就可以在铃铛处等待,等另一个人放了苹果了,就来铃铛处通知,这样两个人就能高效利用资源了!!

铃铛就是我们讲解的条件变量:

1.需要一个线程队列

2.需要有通知机制

  • 全部叫醒
  • 叫醒一个 

1.2.3、测试代码 

新线程等待函数

const int num = 5;
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;void* Wait(void* args)
{std::string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&gmutex);pthread_cond_wait(&gcond,&gmutex);usleep(10000);std::cout << "I am " << name << std::endl;pthread_mutex_unlock(&gmutex);}
}

主函数

int main()
{// 1.创建保存线程tid的数组pthread_t threads[num];for(int i=0;i<num;i++){char* name = new char[1024];snprintf(name,1024,"thread-%d",i + 1);pthread_create(threads + i,nullptr,Wait,(void*)name);usleep(1000);}sleep(1);// 2.唤醒其他线程while(true){// pthread_cond_signal(&gcond); // 唤醒一个线程pthread_cond_broadcast(&gcond); // 唤醒所有线程std::cout << "唤醒一个线程..." << std::endl;sleep(2);}// 3.终止线程for(int i=0;i<num;i++){pthread_join(threads[i],nullptr);}return 0;
}

运行结果 

2、生产消费模型 

2.1、为何要使用生产消费模型

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

2.2、生产者消费者模型优点

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

思考切入点:"321"原则

  • 1、一个交易场所(特定数据结构形式存在的一段内存空间)
  • 2、两种角色(生产角色 消费角色)生产线程,消费线程
  • 3、三种关系(生产和生产[互斥] 消费和消费[互斥] 生产和消费[同步和互斥])

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

2.3、编写生产消费模型

BlockingQueue

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

2.3.1、BlockQueue类基本结构

 此处的类设计成模板形式,让结构更加灵活!!!

template<typename T>
class BlockQueue
{
private:bool IsFull();bool IsEmpty();
public:BlockQueue(int cap = defaultcap);// 消费者出队列void Pop(T* out);// 生产者入队列void Equeue(const T& in);~BlockQueue();
private:std::queue<T> _block_queue; // 临界资源int _max_cap;pthread_mutex_t _mutex;pthread_cond_t _p_cond; // 生产着条件变量pthread_cond_t _c_cond; // 消费者条件变量
};

2.3.2、构造析构函数

构造函数用于初始化最大容量和初始化锁以及条件变量,析构函数用于释放锁和条件变量!

// 构造
BlockQueue(int cap = defaultcap) :_max_cap(cap)
{pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_p_cond,nullptr);pthread_cond_init(&_c_cond,nullptr);
}// 析构
~BlockQueue()
{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_p_cond);pthread_cond_destroy(&_c_cond);
}

2.3.3、判空判满函数

判断是否为空即判断队列是否为空即可,判断是否未满即判断队列成员个数是否与最大容量相等!!

// 判满
bool IsFull()
{return _block_queue.size() == _max_cap;
}
// 判空
bool IsEmpty()
{return _block_queue.empty();
}

2.3.4、生产者入队

入队是将数据插入到队尾中,可能出现数据不一致问题,因此需要加锁和条件变量,如果满了则需要等待,不为满则需要插入数据,并唤醒消费者!!!

// 生产者入队列
void Equeue(const T& in)
{pthread_mutex_lock(&_mutex); // 上锁while(IsFull()){// 满了,生产着不能生产,必须等待// 可是在临界区里面!pthread_cond_wait// 被调用的时候,除了让自己排队等待,还会自己释放传入的锁// 函数返回的时候,不就还在临界区了?// 返回时:必须参与锁的竞争,重新加上锁才能返回pthread_cond_wait(&_p_cond,&_mutex);}// 1.没有满 || 2.被唤醒了_block_queue.push(in); // 生产到阻塞队列pthread_mutex_unlock(&_mutex); // 解锁pthread_cond_signal(&_c_cond); // 唤醒消费者,解锁前解锁后均可
}

2.3.5、消费者出队

出队即删除队头数据,并获取队头的数据,为空则需要等待,不为空则可以删除队头数据,并唤醒生产者!!!

// 消费者出队列
void Pop(T* out)
{pthread_mutex_lock(&_mutex);// 为空,消费者不能消费,必须等待while(IsEmpty()){// 添加尚未满足,但是线程被异常唤醒的情况,叫做伪唤醒!pthread_cond_wait(&_c_cond,&_mutex);}// 1.没有空 || 2.被唤醒*out = _block_queue.front(); // 输出型参数_block_queue.pop();pthread_mutex_unlock(&_mutex);// 唤醒生产着生产pthread_cond_signal(&_p_cond);
}

2.4、测试生产消费模型

2.4.1、内置类型

Consumer

void* Consumer(void* args)
{BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);while(true){// 1.获取数据int t;bq->Pop(&t);// 2.处理数据std::cout << "Consumer->" << t << std::endl;}
}

Productor

void* Productor(void* args)
{srand(time(nullptr) ^ getpid());BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);while(true){// 1.构建数据/任务int x = rand() % 10 + 1; // [1,10]sleep(1); // 1秒生产一个数据// 2.生产数据bq->Equeue(x);std::cout << "Productor->" << x << std::endl;}
}

主函数

int main()
{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;
}

 运行结果 

2.4.2、类类型

Task类

设计一个加法的Task类,内部封装仿函数,测试函数!!!

class Task
{
public:Task(){}// 带参构造Task(int x, int y) : _x(x), _y(y){}// 仿函数,直接使用()访问Excute函数void operator()(){Excute();}void Excute(){_result = _x + _y;}std::string debug(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";return msg;}std::string result(){std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);return msg;}
private:int _x;int _y;int _result;
};

Consumer

void *Consumer(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){// 1.获取数据Task t;bq->Pop(&t);// 2.处理数据// t.Excute();t(); // 使用仿函数std::cout << "Consumer->" << t.result() << std::endl;}
}

Productor

void *Productor(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){// 1.构建数据/任务int x = rand() % 10 + 1; // [1,10]usleep(1000);            // 尽量保证随机数不同int y = rand() % 10 + 1;Task t(x,y);// 2.生产数据bq->Equeue(t);std::cout << "Productor->" << t.debug() << std::endl;sleep(1);}
}

主函数

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;
}

运行结果  

2.4.3、函数类型

函数与声明与实现

// typedef std::function<void()> task_t;
using task_t = std::function<void()>; // 包装器void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}

Consumer

void *Consumer(void *args)
{BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);while (true){// 1.获取数据task_t t;bq->Pop(&t);// 2.处理数据t(); // 使用仿函数}
}

Productor

void *Productor(void *args)
{BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);while (true){// 1.生产数据bq->Equeue(Download);std::cout << "Productor-> Download" << std::endl;sleep(1);}
}

主函数

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;
}

运行结果  

2.4.4、多生产多消费 

int main()
{BlockQueue<task_t> *bq = new BlockQueue<task_t>();// 多生产 多消费pthread_t c1,c2,p1,p2,p3;// 创建线程pthread_create(&c1, nullptr, Consumer, bq);pthread_create(&c2, nullptr, Consumer, bq);pthread_create(&p1, nullptr, Productor, bq);pthread_create(&p2, nullptr, Productor, bq);pthread_create(&p3, nullptr, Productor, bq);// 终止线程pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(p3, nullptr);return 0;
}

运行结果  

相关文章:

【Linux系统编程】第四十六弹---线程同步与生产消费模型深度解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、Linux线程同步 1.1、同步概念与竞态条件 1.2、条件变量 1.2.1、认识条件变量接口 1.2.2、举例子认识条件变量 1.2.3、…...

VoIP是什么?

IP 语音 (VoIP)&#xff08;Voice over Internet Protocol&#xff09; 是一种通过互联网拨打电话的方法。与旧的固定电话系统不同&#xff0c;互联网并非设计用于在连接的人之间实时传输音频信号。必须构建专门的技术和协议才能使之成为可能&#xff0c;这些技术和协议构成了 …...

MySQL 中的集群部署方案

文章目录 MySQL 中的集群部署方案MySQL ReplicationMySQL Group ReplicationInnoDB ClusterInnoDB ClusterSetInnoDB ReplicaSetMMMMHAGalera ClusterMySQL ClusterMySQL Fabric 总结参考 MySQL 中的集群部署方案 MySQL Replication MySQL Replication 是官方提供的主从同步方…...

《设计模式》创建型模式总结

目录 创建型模式概述 Factory Method: 唯一的类创建型模式 Abstract Factory Builder模式 Prototype模式 Singleton模式 最近在参与一个量化交易系统的项目&#xff0c;里面涉及到用java来重构部分vnpy的开源框架&#xff0c;因为是框架的搭建&#xff0c;所以会涉及到像…...

Conda安装与使用中的若干问题记录

Conda安装与使用中的若干问题记录 1.Anaconda 安装失败1.1.问题复述1.2.问题解决&#xff08;安装建议&#xff09; 2.虚拟环境pip install未安装至本虚拟环境2.1.问题复述2.2.问题解决 3.待补充 最近由于工作上的原因&#xff0c;要使用到Conda进行虚拟环境的管理&#xff0c;…...

人力资源招聘系统的革新之路:从传统到智能的转变

在全球化与数字化交织的今天&#xff0c;企业间的竞争日益激烈&#xff0c;而人才作为企业发展的核心驱动力&#xff0c;其重要性不言而喻。传统的人力资源招聘方式&#xff0c;如依赖纸质简历、人工筛选、面对面面试等&#xff0c;不仅效率低下&#xff0c;且难以精准匹配企业…...

Python网络爬虫与数据采集实战——网络协议与HTTP

目录 1. HTTP协议简介 2. 常见的请求方法 3. 状态码含义 实际应用中的HTTP协议 1. 如何在爬虫中使用HTTP协议 2. 模拟浏览器请求与爬虫反爬虫技术 3. 高级HTTP请求 实现爬虫时HTTP协议的优化与常见问题 总结 1. HTTP协议简介 HTTP的定义与作用 HTTP&#xff08;超文本…...

从零开始的c++之旅——二叉搜索树

1、二叉搜索树概念 1. ⼆叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空&#xff0c;则左⼦树上所有结点的值都⼩于等于根结点的值 • 若它的右⼦树不为空&#xff0c;则右⼦树上所有结…...

CSS回顾-基础知识详解

一、引言 在前端开发领域&#xff0c;CSS 曾是构建网页视觉效果的关键&#xff0c;与 HTML、JavaScript 一起打造精彩的网络世界。但随着组件库的大量涌现&#xff0c;我们亲手书写 CSS 样式的情况越来越少&#xff0c;CSS 基础知识也逐渐被我们遗忘。 现在&#xff0c;这种遗…...

Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别

Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别 keyword 与 text 区别term 查询match 查询match_phrase 查询match_phrase_prefix 查询写在最后 在讲述 es 查询时 term、match、match_phrase、match_phrase_prefix 的区别之前&#xff0c;先来了…...

低代码平台:跨数据库处理的重要性与实现方式

一、低代码平台概述 低代码平台作为一种创新的软件开发工具&#xff0c;为开发者带来了极大的便利。它具备可视化编程工具和大量预构建组件&#xff0c;这使得开发者无需编写大量代码就能创建应用程序&#xff0c;显著降低了软件开发的技术门槛。无论是专业开发人员还是业务人员…...

【jvm】如何破坏双亲委派机制

目录 1.说明2.重写ClassLoader的loadClass方法2.1 原理2.2 实现步骤2.3 注意事项 3.使用线程上下文类加载器3.1 原理3.2 实现步骤3.3 应用场景 4.利用SPI机制4.1 原理4.2 实现步骤4.3 应用场景 5.Tomcat等容器的自定义类加载器5.1 原理5.2 实现方式5.3 应用场景 1.说明 1.双亲委…...

ReactPress与WordPress:一场内容管理系统的较量

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress WordPress官网&#xff1a;https://wordpress.org/ ReactPress与WordPress&#xff1a;一场内容管理系统的较量 在当今数字化时代&#xff0c;内容管理系统&#xff08;CMS&#xff09;已成为…...

网络安全练习之 ctfshow_web

文章目录 VIP题目限免&#xff08;即&#xff1a;信息泄露题&#xff09;源码泄露前台JS绕过协议头信息泄露robots后台泄露phps源码泄露源码压缩包泄露版本控制泄露源码(git)版本控制泄露源码2(svn)vim临时文件泄露cookie泄露域名txt记录泄露敏感信息公布内部技术文档泄露编辑器…...

在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别

在 Service Worker 中&#xff0c;caches.put(request, response) 和 caches.add(request)/caches.addAll(requests) 方法都用于将资源添加到缓存中&#xff0c;但它们的使用场景和目的略有不同。 caches.put(request, response)&#xff0c;一用在fetch事件当中&#xff0c;由…...

UNIAPP发布小程序调用讯飞在线语音合成+实时播报

语音合成能够将文字转化为自然流畅的人声&#xff0c;提供100发音人供您选择&#xff0c;支持多语种、多方言和中英混合&#xff0c;可灵活配置音频参数。广泛应用于新闻阅读、出行导航、智能硬件和通知播报等场景。 在当下大模型火爆的今日&#xff0c;语音交互页离不开语音合…...

跳房子(弱化版)

题目描述 跳房子&#xff0c;也叫跳飞机&#xff0c;是一种世界性的儿童游戏&#xff0c;也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下&#xff1a; 在地面上确定一个起点&#xff0c;然后在起点右侧画 n 个格子&#xff0c;这些格子都在同一条直线上。每个格子内…...

ubuntu22 安装 minikube

在Ubuntu 22上安装Minikube&#xff0c;你可以按照以下步骤进行&#xff1a; 安装依赖&#xff1a; 更新系统并安装必要的依赖项&#xff1a; sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl安装Docker&#xff1a; Minikube可以使用D…...

STM32 | 超声波避障小车

超声波避障小车 一、项目背题 由于超声波测距是一种非接触检测技术&#xff0c;不受光线、被测对象颜色等的影响&#xff0c;较其它仪器更卫生&#xff0c;更耐潮湿、粉尘、高温、腐蚀气体等恶劣环境&#xff0c;具有少维护、不污染、高可靠、长寿命等特点。因此可广泛应用于…...

打造旅游卡服务新标杆:构建SOP框架与智能知识库应用

随着旅游业的蓬勃兴起&#xff0c;旅游卡产品正逐渐成为市场的焦点。为了进一步提升服务质量和客户体验&#xff0c;构建一套高效且标准化的操作流程&#xff08;SOP&#xff09;变得尤为重要。本文将深入探讨如何构建旅游卡的SOP框架&#xff0c;并介绍如何利用智能知识库技术…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...