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

Linux:生产者消费者模型

一、普通生产者消费者模型

1.1 什么是生产者消费者模型

       现实生活中,我们也会有像生物世界的生产者和消费者的概念,但是我们的消费者在大多数情况下并不和生产者直接联系,就比如说食物,不能说我今天去找供货商要十个面包,然后我还得在那等他把十个面包生产完了再走,虽然这对于生产者来说有多少需求就供应多少节约了成本,但是对于消费者来说却浪费了很多时间,我们作为消费者肯定希望我们去买的时候就能够买到,因此这个时候我们需要一个中间场所——超市,供应商可以一次性先生产一部分面包,然后把他摆到超市的货架上,这样消费者来拿的时候就可以直接拿了! 而当货架上快空了的时候,超市可以告知消费者先等一段时间再过来,然后通知生产者尽快生产,而货架满的时候,超市可以通知生产者先不要生产,而是让消费者尽量来消费。所以我们会发现这个中间场所让我们的交互变得更加理性化,我们生产者和消费者并不关心对方,而又超市这个中间场所来平衡两边的能力,这就是生产者消费者模型!!

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

      基于这段共享内存,他就会存在并发问题,因此可能会有以下三种关系

生产者vs生产者:因为空间有限,所以生产者和生产者是竞争关系,需要互斥

消费者vs消费者:因为资源有限(比如世界末日),所以消费者和消费者是竞争关系,需要互斥

生产者vs消费者: 因为有可能会出现生产者边生产,消费者边拿的情况,所以拿到货物必须要保证货物先被生产完毕,所以需要互斥!而超市可以被生产者和消费者都访问到,因此当我没有资源的时候,我并不希望消费者一直来访问我,当我资源特别多的时候,我希望生产者不要一直来访问我,因此我们希望消费者和生产者按照一定的顺序去访问资源,所以需要有同步!(要在互斥保证线程安全的前提下)

“321”原则:3个关系,2个角色,1个特定结构的内存空间 

计算机场景1:cpu/内存/外设

计算机场景2:main/参数池/add

        比如以前我们在main函数里调用add函数,我们把参数传给他之后当add函数在执行的时候,main函数会在那里等,这是单执行流。

       可是这次如果我们将main函数和add分为两个线程,add要执行的时候必然要保证main给他传递参数,所以此时他们就相当于一个生产者消费者模型,我们可以将参数统一放在一个空间里,让add去取,然后通过互斥和同步来实现解耦,使他具有并发性。

问题:可是我生产者生产资源后放在空间里,再让消费者来拿,解耦是做到了,可是他还需要加锁和解锁,所以他的高效性究竟体现在哪里呢? 

——>

生产者的数据从哪里来??——>用户/网络等!!

所以生产者生产所需要的数据是需要花时间获取的!!!

所以第一步是获取数据,第二步才是生产数据到队列中!!

消费者对数据做加工也要花时间!!

所以第一步是消费数据,第二步是加工处理数据!!

因此高效性体现在一方访问临界资源,另一方访问非临界资源,此时两个线程并发访问!

1.2 快速实现cp 

假设我们当前想要实现一方通过传递两个参数,另一方接受参数并进行计算。

类的设计: 

#pragma once
#include <iostream>
#include <string>std::string opers="+-*/%";enum{DivZero=1,ModZero,Unknown
};class Task
{
public:Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if(data2_ == 0) exitcode_ = DivZero;else result_ = data1_ / data2_;}break;case '%':{if(data2_ == 0) exitcode_ = ModZero;else result_ = data1_ % data2_;}            break;default:exitcode_ = Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=";r += std::to_string(result_);r += "[code: ";r += std::to_string(exitcode_);r += "]";return r;}std::string GetTask(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};

问题1:为什么要用类对象传参数,而不是直接定义几个变量去传参数呢??

——> 传类对象的好处(1)内部可以直接放很多我们所需要的内置类型,还可以把处理结果也放进去 (2)可以顺便把我们的执行任务写到类方法里 (3)可扩展性高,未来想加参数或者方法可以直接去类里面加!

阻塞队列实现:

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>template <class T>
class BlockQueue
{static const int defalutnum = 20;
public:BlockQueue(int maxcap = defalutnum):maxcap_(maxcap){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&c_cond_, nullptr);pthread_cond_init(&p_cond_, nullptr);// low_water_ = maxcap_/3;// high_water_ = (maxcap_*2)/3;}// 谁来唤醒呢?T pop(){pthread_mutex_lock(&mutex_);while(q_.size() == 0) // 因为判断临界资源调试是否满足,也是在访问临界资源!判断资源是否就绪,是通过再临界资源内部判断的。{// 如果线程wait时,被误唤醒了呢??pthread_cond_wait(&c_cond_, &mutex_); // 你是持有锁的!!1. 调用的时候,自动释放锁,因为唤醒而返回的时候,重新持有锁}T out = q_.front(); // 你想消费,就直接能消费吗?不一定。你得先确保消费条件满足q_.pop();// if(q_.size()<low_water_) pthread_cond_signal(&p_cond_);pthread_cond_signal(&p_cond_); // pthread_cond_broadcastpthread_mutex_unlock(&mutex_);return out;}void push(const T &in){pthread_mutex_lock(&mutex_);while(q_.size() == maxcap_){ // 做到防止线程被伪唤醒的情况// 伪唤醒情况pthread_cond_wait(&p_cond_, &mutex_); //1. 调用的时候,自动释放锁 2.?}// 1. 队列没满 2.被唤醒 q_.push(in);                    // 你想生产,就直接能生产吗?不一定。你得先确保生产条件满足// if(q_.size() > high_water_) pthread_cond_signal(&c_cond_);pthread_cond_signal(&c_cond_);pthread_mutex_unlock(&mutex_);}~BlockQueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}
private:std::queue<T> q_; // 共享资源, q被当做整体使用的,q只有一份,加锁。但是共享资源也可以被看做多份!//int mincap_;int maxcap_;      // 极值pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;// int low_water_;// int high_water_;
};

需要有两个条件变量,一个条件变量用来提醒生产者放数据,另一个用来体系消费者拿数据

问题2:为什么加锁之后要加个判断呢??

——>为了判断临界资源是否就绪!!所以要访问一下临界资源的数量

问题3:可是我们难道一有数据就唤醒消费者,一没数据就唤醒生产者吗??我可不可以就是当生产达到一定数量的时候在唤醒消费者,或者是消费者消费到一定数量的时候再唤醒生产者,这样是不是就减少了唤醒的次数??

——>设置一个low_water和high_water 低于low的时候才唤醒生产者,当高于high的时候才唤醒消费者,而在中间的时候就正常去并发,不去刻意唤醒

执行:

#include "BlockQueue.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <ctime>void *Consumer(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){// 消费Task t = bq->pop();// 计算// t.run();t();std::cout << "处理任务: " << t.GetTask() << " 运算结果是: " << t.GetResult() << " thread id: " << pthread_self() << std::endl;// t.run();// sleep(1);}
}void *Productor(void *args)
{int len = opers.size();BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);int x = 10;int y = 20;while (true){// 模拟生产者生产数据int data1 = rand() % 10 + 1; // [1,10]usleep(10);int data2 = rand() % 10;char op = opers[rand() % len];Task t(data1, data2, op);// 生产bq->push(t);std::cout << "生产了一个任务: " << t.GetTask() << " thread id: " << pthread_self() << std::endl;sleep(1);}
}int main()
{srand(time(nullptr));// 因为 321 原则// BlockQueue 内部可不可以传递其他数据,比如对象?比如任务???BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c[3], p[5];for (int i = 0; i < 3; i++){pthread_create(c + i, nullptr, Consumer, bq);}for (int i = 0; i < 5; i++){pthread_create(p + i, nullptr, Productor, bq);}for (int i = 0; i < 3; i++){pthread_join(c[i], nullptr);}for (int i = 0; i < 5; i++){pthread_join(p[i], nullptr);}delete bq;return 0;
}

 问题4:多生产者消费者的时候,为什么我们只要加个for循环就可以做到了?

——>因为321原则,而无论有多少个线程,大家用的都是同一把锁(互斥),同样的两个条件变量(同步)

1.3 伪唤醒问题

 比如说当前有3个生产者在阻塞状态,但是你同时把3个都唤醒了,唤醒之后必须持有锁,所以3个开始竞争,其中一个竞争成功并产生了资源,但是此时恰好队列已经满了,在他释放锁在的时候,另外两个被唤醒的资源并不在阻塞队列中,所以他们并不知道队列已经满了,于是产生了问题!!

——>所以我们的解决方法就是对于临界资源的访问要循环去访问!!这样的话那俩线程如果发现条件不满足他会继续进入休眠状态!

 二、环形队列的生产消费模型

 2.1 信号量铺垫

信号量的本质就是一把保证PV操作的计数器,而这个计数器就是用来描述临界资源的资源数目的

所以我们需要将资源是否就绪放在临界区之外,所以申请信号量的时候,其实就间接在做判断了

信号量保证不让多余的人进来,而让进来的人到自己的位置上就是我们编码要解决的! 

 2.2 基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性

         环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来 判断满或者空。另外也可以预留一个空的位置,作为满的状态 

 但是我们有信号量,我们就可以用信号量来充当这个计数器!而P和C关注的资源不一样,因此我们需要定义两把信号量spacesem(N)表示剩余空间,datasem(0)表示剩余数据

而他们的操作如下图 ,当P占了一个空间,也以为着C多了一个资源   而C消费了一个资源  也意味着P多了一个空间   ,此时是并发访问的

而当空间没的时候,P占不了了就会被挂起,资源没的时候,C消费不了也会被挂起!

问题1:我们两个什么时候才会指向同一个位置呢??

——>空或者满,而不空或者不满的时候,我们一定只想不同的位置,我们可以同时访问!! 

规则总结:

(1)指向同一个位置的时候只能有一个人访问

空:生产者

满:消费者

(2)消费者不能超过生产者

(3)生产者不能把消费者套一个圈 

——>所以他们申请自己的资源,释放对方的资源,就友好地满足了一个追逐游戏必须具备的三个条件!!

问题2 :环形队列有什么特点?

——> 用两个信号量来分别充当资源的计数器,申请信号量如果成功就说明资源必然就绪了!(对资源预定成功)但是具体哪个线程去访问哪块资源,是由我们编码决定的 (就相当于我们去电影院能买到票,那么我们就可以进到电影院,但是具体要坐哪个位置,是需要我们去控制的)

(1)当为空或为满时,生产者和消费者只能有一个在执行,体现了局部的互斥性

(2)当为空时必须生产者先执行,为满时必须消费者先执行,体现了局部的同步性

(3)当不为空或者不为满时,生产者和消费者可以同时并发访问临界资源,体现了并发的高效性

(4)生产者和生产者之间以及消费者与消费者之间会竞争下标资源,需要互斥加锁,且锁要加在信号量的后面,因为信号量是原子的,不需要保护。

2.3 POSIX信号量接口介绍 

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

1,初始化信号量

#include <semaphore.h>

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

参数:

pshared:0表示线程间共享,非零表示进程间共享

value:信号量初始值

2,销毁信号量

int sem_destroy(sem_t *sem); 

3,等待信号量

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

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

4,发布信号量 

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

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

2.4 快速实现 

 环形队列的设计

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>const static int defaultcap = 5;template<class T>
class RingQueue{
private:void P(sem_t &sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}
public:RingQueue(int cap = defaultcap):ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0){sem_init(&cdata_sem_, 0, 0);sem_init(&pspace_sem_, 0, cap);pthread_mutex_init(&c_mutex_, nullptr);pthread_mutex_init(&p_mutex_, nullptr);}void Push(const T &in) // 生产{P(pspace_sem_);Lock(p_mutex_); // ?ringqueue_[p_step_] = in;// 位置后移,维持环形特性p_step_++;p_step_ %= cap_;Unlock(p_mutex_); V(cdata_sem_);}void Pop(T *out)       // 消费{P(cdata_sem_);Lock(c_mutex_); // ?*out = ringqueue_[c_step_];// 位置后移,维持环形特性c_step_++;c_step_ %= cap_;Unlock(c_mutex_); V(pspace_sem_);}~RingQueue(){sem_destroy(&cdata_sem_);sem_destroy(&pspace_sem_);pthread_mutex_destroy(&c_mutex_);pthread_mutex_destroy(&p_mutex_);}
private:std::vector<T> ringqueue_;int cap_;int c_step_;       // 消费者下标int p_step_;       // 生产者下标sem_t cdata_sem_;  // 消费者关注的数据资源sem_t pspace_sem_; // 生产者关注的空间资源pthread_mutex_t c_mutex_;pthread_mutex_t p_mutex_;
};

问题1:加锁应该在申请信号量的前面还是申请信号量的后面??

——>应该在申请信号量的后面(2方案),从技术角度,信号量的操作是原子的所以并不需要被保护。从效率角度,2相比1来说,当一个线程抢到锁的时候,其他线程与其在这里干等,还不如先去把信号量资源给申请了!

任务文件:

#pragma once
#include <iostream>
#include <string>std::string opers="+-*/%";enum{DivZero=1,ModZero,Unknown
};class Task
{
public:Task(){}Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if(data2_ == 0) exitcode_ = DivZero;else result_ = data1_ / data2_;}break;case '%':{if(data2_ == 0) exitcode_ = ModZero;else result_ = data1_ % data2_;}            break;default:exitcode_ = Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=";r += std::to_string(result_);r += "[code: ";r += std::to_string(exitcode_);r += "]";return r;}std::string GetTask(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};

主函数: 

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include "RingQueue.hpp"
#include "Task.hpp"using namespace std;struct ThreadData
{RingQueue<Task> *rq;std::string threadname;
};void *Productor(void *args)
{// sleep(3);ThreadData *td = static_cast<ThreadData*>(args);RingQueue<Task> *rq = td->rq;std::string name = td->threadname;int len = opers.size();while (true){// 1. 获取数据int data1 = rand() % 10 + 1;usleep(10);int data2 = rand() % 10;char op = opers[rand() % len];Task t(data1, data2, op);// 2. 生产数据rq->Push(t);cout << "Productor task done, task is : " << t.GetTask() << " who: " << name << endl;sleep(1);}return nullptr;
}void *Consumer(void *args)
{ThreadData *td = static_cast<ThreadData*>(args);RingQueue<Task> *rq = td->rq;std::string name = td->threadname;while (true){// 1. 消费数据Task t;rq->Pop(&t);// 2. 处理数据t();cout << "Consumer get task, task is : " << t.GetTask() << " who: " << name << " result: " << t.GetResult() << endl;// sleep(1);}return nullptr;
}int main()
{srand(time(nullptr) ^ getpid());RingQueue<Task> *rq = new RingQueue<Task>(50);pthread_t c[5], p[3];for (int i = 0; i < 1; i++){ThreadData *td = new ThreadData();td->rq = rq;td->threadname = "Productor-" + std::to_string(i);pthread_create(p + i, nullptr, Productor, td);}for (int i = 0; i < 1; i++){ThreadData *td = new ThreadData();td->rq = rq;td->threadname = "Consumer-" + std::to_string(i);pthread_create(c + i, nullptr, Consumer, td);}for (int i = 0; i < 1; i++){pthread_join(p[i], nullptr);}for (int i = 0; i < 1; i++){pthread_join(c[i], nullptr);}return 0;
}

相关文章:

Linux:生产者消费者模型

一、普通生产者消费者模型 1.1 什么是生产者消费者模型 现实生活中&#xff0c;我们也会有像生物世界的生产者和消费者的概念&#xff0c;但是我们的消费者在大多数情况下并不和生产者直接联系&#xff0c;就比如说食物&#xff0c;不能说我今天去找供货商要十个面包&#xff…...

网络安全 | F5-Attack Signatures详解

关注&#xff1a;CodingTechWork 关于攻击签名 攻击签名是用于识别 Web 应用程序及其组件上攻击或攻击类型的规则或模式。安全策略将攻击签名中的模式与请求和响应的内容进行比较&#xff0c;以查找潜在的攻击。有些签名旨在保护特定的操作系统、Web 服务器、数据库、框架或应…...

自然元素有哪些选择?

在设计浪漫风格的壁纸时&#xff0c;自然元素是营造温馨、梦幻氛围的关键。以下是一些常见的自然元素选择&#xff0c;以及它们在壁纸设计中建议&#xff1a; 一、花朵 玫瑰&#xff1a; 特点&#xff1a;玫瑰是浪漫的象征&#xff0c;尤其是红色和粉色玫瑰&#xff0c;能够传…...

基于微信阅读网站小程序的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

linux挂载新硬盘,查看新硬盘,格式化分区,创建挂载点,挂载逻辑卷,整盘方式挂载,LVM方式挂载,查看linux 磁盘卷组的剩余空间,ext4与xfs区别

摘要 挂载新硬盘&#xff0c;本文作者整理了几乎所有相关的知识点 作者采用的是本文第二种挂载方式&#xff08;LVM&#xff09;&#xff0c;只用了下面6条命令搞定 # 说明&#xff1a; # /dev/mapper/appvg-mylv1 逻辑卷完整名称 # # /dev/mapper目录是Linux系统中用…...

Web3.0时代的挑战与机遇:以开源2+1链动模式AI智能名片S2B2C商城小程序为例的深度探讨

摘要&#xff1a;Web3.0作为互联网的下一代形态&#xff0c;承载着去中心化、开放性和安全性的重要愿景。然而&#xff0c;其高门槛、用户体验差等问题阻碍了Web3.0的主流化进程。本文旨在深入探讨Web3.0面临的挑战&#xff0c;并提出利用开源21链动模式、AI智能名片及S2B2C商城…...

AIGC专栏18——EasyAnimateV5.1版本详解 应用Qwen2 VL作为文本编码器,支持轨迹控制与相机镜头控制

AIGC专栏18——EasyAnimateV5.1版本详解 应用Qwen2 VL作为文本编码器&#xff0c;支持轨迹控制与相机镜头控制 学习前言相关地址汇总源码下载地址HF测试链接MS测试链接 测试效果Image to VideoText to Video轨迹控制镜头控制 EasyAnimate详解技术储备Qwen2 VLStable Diffusion …...

测试的基本原则

1&#xff0e;SDLC 才是王道&#xff1a;软件开发生命周期&#xff08;SDLC&#xff09;对于软件开发而言&#xff0c;是如同基石般的关键流程&#xff0c;每一位开发人员都应该对其了如指掌。从最初的需求定义&#xff0c;到最终软件上线后的维护&#xff0c;SDLC 的各个阶段环…...

如何建设一个企业级的数据湖

建设一个企业级的数据湖是一项复杂且系统化的工程&#xff0c;需要从需求分析、技术选型、架构设计到实施运维等多个方面进行综合规划和实施。以下是基于我搜索到的资料&#xff0c;详细阐述如何建设企业级数据湖的步骤和关键要点&#xff1a; 一、需求分析与规划 明确业务需…...

Ubuntu介绍、与centos的区别、基于VMware安装Ubuntu Server 22.04、配置远程连接、安装jdk+Tomcat

目录 ?编辑 一、Ubuntu22.04介绍 二、Ubuntu与Centos的区别 三、基于VMware安装Ubuntu Server 22.04 下载 VMware安装 1.创建新的虚拟机 2.选择类型配置 3.虚拟机硬件兼容性 4.安装客户机操作系统 5.选择客户机操作系统 6.命名虚拟机 7.处理器配置 8.虚拟机内存…...

springfox-swagger-ui 3.0.0 配置

在3.0中&#xff0c;访问地址URL变了。 http://地址:端口/项目名/swagger-ui/ SpringBoot maven项目引入 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>3.0.0</version> </…...

【PyTorch][chapter 29][李宏毅深度学习]Fine-tuning LLM

参考&#xff1a; https://www.youtube.com/watch?veC6Hd1hFvos 目录&#xff1a; 什么是 Fine-tune 为什么需要Fine-tuning 如何进行Fine-tune Fine-tuning- Supervised Fine-tuning 流程 Fine-tuning参数训练的常用方案 LORA 简介 示例代码 一 什么是 Fine-tune …...

Spring无法解决的循环依赖

在Spring框架中&#xff0c;循环依赖是指两个或多个Bean相互依赖&#xff0c;形成一个闭环。例如&#xff0c;Bean A依赖于Bean B&#xff0c;而Bean B又依赖于Bean A。虽然Spring通过三级缓存&#xff08;一级缓存、二级缓存、三级缓存&#xff09;机制解决了大多数情况下的循…...

C++的类Class

文章目录 一、C的struct和C的类的区别二、关于OOP三、举例&#xff1a;一个商品类CGoods四、构造函数和析构函数1、定义一个顺序栈2、用构造和析构代替s.init(5);和s.release();3、在不同内存区域构造对象4、深拷贝和浅拷贝5、构造函数和深拷贝的简单应用6、构造函数的初始化列…...

如何应对离别之:短暂离别

《若道离别》&#xff08;一&#xff09;&#xff1a;如何应对离别之短暂离别 大多数人还是不能很全心愉快地面对离别&#xff0c;哪怕只是短暂&#xff0c;还是从有到无的失落感&#xff0c;有人一天就适应&#xff0c;有人需要很久 不求离别无动于衷&#xff0c;但求使用部分…...

Harmony Next 跨平台开发入门

ArkUI-X 官方介绍 官方文档&#xff1a;https://gitee.com/arkui-x/docs/tree/master/zh-cn ArkUI跨平台框架(ArkUI-X)进一步将ArkUI开发框架扩展到了多个OS平台&#xff1a;目前支持OpenHarmony、Android、 iOS&#xff0c;后续会逐步增加更多平台支持。开发者基于一套主代码…...

笔试-二维数组2

应用 现有M(1<M<10)个端口组&#xff0c;每个端口组是长度为N(1<N<100)&#xff0c;元素均为整数。如果这些端口组间存在2个及以上的元素相同&#xff0c;则认为端口组可以关联合并&#xff1b;若可以关联合并&#xff0c;请用二位数组表示输出结果。其中&#xf…...

/opt安装软件,就可以使用man xx命令是为什么

引言 以neovim的安装过程为例 下载 curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz sudo rm -rf /opt/nvim sudo tar -C /opt -xzf nvim-linux64.tar.gz添加环境变量前&#xff0c;是无法使用man nvim的 Then add this to your sh…...

vue3和vue2的区别有哪些差异点

Vue3 vs Vue2 主要差异对比指南 官网 1. 核心架构差异 1.1 响应式系统 Vue2&#xff1a;使用 Object.defineProperty 实现响应式 // Vue2 响应式实现 Object.defineProperty(obj, key, {get() {// 依赖收集return value},set(newValue) {// 触发更新value newValue} })Vue3…...

记录备战第十六届蓝桥杯的过程

1.学会了原来字符串也有比较方法&#xff0c;也就是字符串987 > 98 等等&#xff0c;可以解决拼最大数问题 题目链接&#xff1a;5.拼数 - 蓝桥云课 (lanqiao.cn) 2.今天又复习了一下bfs&#xff0c;感觉还是很不熟练&#xff0c;可能是那个过程我些许有点不熟悉&#xff…...

【PVE】Proxmox VE8.0+创建LXC容器安装docker

为了不影响PVE宿主机&#xff0c;通常使用套娃的形式安装Docker容器&#xff0c;再安装相关docker应用。首先在CT模板中创建 Linux 容器&#xff0c;推荐使用Debian。开启ssh登录&#xff0c;修改debian配置&#xff0c;安装docker 一、创建 LXC 容器 1、CT模板下载 点击“模…...

Semantic Kernel - Kernel理解

目录 一、关于Kernel 二、案例实战 三、运行截图 一、关于Kernel 微软的 Semantic Kernel 项目中,Semantic Kernel 是一个工具框架,旨在使得开发人员能够更容易地将大语言模型(如GPT)集成到不同的应用中。它通过提供一组接口、任务模板和集成模块,使开发者能够轻松地设计…...

【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南

文章目录 &#x1f30d;一. WEB 开发❄️1. 介绍 ❄️2. BS 与 CS 开发介绍 ❄️3. JavaWeb 服务软件 &#x1f30d;二. Tomcat❄️1. Tomcat 下载和安装 ❄️2. Tomcat 启动 ❄️3. Tomcat 启动故障排除 ❄️4. Tomcat 服务中部署 WEB 应用 ❄️5. 浏览器访问 Web 服务过程详…...

「 机器人 」利用冲程对称性调节实现仿生飞行器姿态与方向控制

前言 在仿生扑翼飞行器中,通过改变冲程对称性这一技术手段,可以在上冲与下冲两个阶段引入不对称性,进而产生额外的力或力矩,用于实现俯仰或其他姿态方向的控制。以下从原理、在仿生飞行器中的应用和典型实验示例等方面进行梳理与阐述。 1. 冲程对称性原理 1.1 概念:上冲与…...

力扣算法题——11.盛最多水的容器

目录 &#x1f495;1.题目 &#x1f495;2.解析思路 本题思路总览 借助双指针探索规律 从规律到代码实现的转化 双指针的具体实现 代码整体流程 &#x1f495;3.代码实现 &#x1f495;4.完结 二十七步也能走完逆流河吗 &#x1f495;1.题目 &#x1f495;2.解析思路…...

企业微信SCRM开创客户管理新纪元推动私域流量高效转化

内容概要 在当今瞬息万变的数字化时代&#xff0c;企业面临着前所未有的客户管理挑战。消费者的需求日益多样化&#xff0c;他们希望能够随时随地与品牌沟通。因此&#xff0c;越来越多的企业意识到&#xff0c;传统的客户管理方式已无法满足市场的需求。在这样的背景下&#…...

C++和Python实现SQL Server数据库导出数据到S3并导入Redshift数据仓库

用C实现高性能数据处理&#xff0c;Python实现操作Redshift导入数据文件。 在Visual Studio 2022中用C和ODBC API导出SQL Server数据库中张表中的所有表的数据为CSV文件格式的数据流&#xff0c;用逗号作为分隔符&#xff0c;用双引号包裹每个数据&#xff0c;字符串类型的数据…...

ESP8266 NodeMCU与WS2812灯带:实现多种花样变换

在现代电子创意项目中&#xff0c;LED灯带的应用已经变得极为广泛。通过结合ESP8266 NodeMCU的强大处理能力和FastLED库的高效功能&#xff0c;我们可以轻松实现多达100种灯带变换效果。本文将详细介绍如何使用Arduino IDE编程&#xff0c;实现从基础到高级的灯光效果&#xff…...

OpenAI 发布首个 AI 智能体

OpenAI 发布首个 AI 智能体 当地时间 1 月 23 日&#xff0c;OpenAI 发布了首个 AI 智能体 Operator124。以下是关于它的详细介绍2&#xff1a; 功能用途 操作网页&#xff1a;可模拟人类操作网页浏览器&#xff0c;能进行点击、滚动、输入等操作&#xff0c;例如在 OpenTable…...

【Linux】gcc/g++的使用

目录 一、gcc/g简介 二、编译和链接 预处理 编译 汇编 连接&#xff08;生成可执行文件或库文件&#xff09; 三、动态链接和静态链接 静态库和动态库 gcc其他常用选项 合集传送门&#xff1a;Linux_uyeonashi的博客-CSDN博客 一、gcc/g简介 GCC&#xff08;GNU Com…...

Kmesh v1.0 正式发布!7 大特性提升网络流量管理效率和安全性

Kmesh v1.0 正式发布&#xff01;7 大特性提升网络流量管理效率和安全性 2025 年新年伊始&#xff0c;Kmesh 团队正式发布了 Kmesh v1.0234。以下是 Kmesh v1.0 提升网络流量管理效率和安全性的 7 大特性35&#xff1a; 加密通信&#xff1a;引入 IPsec 协议对节点间流量加密&a…...

Day45:元组的创建

在 Python 中&#xff0c;元组&#xff08;tuple&#xff09;是一种不可变的序列类型。与列表&#xff08;list&#xff09;不同&#xff0c;元组一旦创建就无法修改它们的内容。元组是有序的&#xff0c;可以包含不同类型的元素&#xff0c;支持索引和切片操作&#xff0c;但不…...

Rust:如何动态调用字符串定义的 Rhai 函数?

在 Rust 中使用 Rhai 脚本引擎时&#xff0c;你可以动态地调用传入的字符串表示的 Rhai 函数。Rhai 是一个嵌入式脚本语言&#xff0c;专为嵌入到 Rust 应用中而设计。以下是一个基本示例&#xff0c;展示了如何在 Rust 中调用用字符串传入的 Rhai 函数。 首先&#xff0c;确保…...

在 Ubuntu22.04 上安装 Splunk

ELK感觉太麻烦了&#xff0c;换个日志收集工具 Splunk 是一种 IT 工具&#xff0c;可帮助在任何设备上收集日志、分析、可视化、审计和创建报告。简单来说&#xff0c;它将“机器生成的数据转换为人类可读的数据”。它支持从虚拟机、网络设备、防火墙、基于 Unix 和基于 Windo…...

单片机基础模块学习——数码管(二)

一、数码管模块代码 这部分包括将数码管想要显示的字符转换成对应段码的函数&#xff0c;另外还包括数码管显示函数 值得注意的是对于小数点和不显示部分的处理方式 由于小数点没有单独占一位&#xff0c;所以这里用到了两个变量i,j用于跳过小数点导致的占据其他字符显示在数…...

DAY01 面向对象回顾、继承、抽象类

学习目标 能够写出类的继承格式public class 子类 extends 父类{}public class Cat extends Animal{} 能够说出继承的特点子类继承父类,就会自动拥有父类非私有的成员 能够说出子类调用父类的成员特点1.子类有使用子类自己的2.子类没有使用,继承自父类的3.子类父类都没有编译报…...

LeetCode:40. 组合总和 II(回溯 + 剪枝 Java)

目录 40. 组合总和 II 题目描述&#xff1a; 实现代码与解析&#xff1a; 回溯 剪枝 原理思路&#xff1a; 40. 组合总和 II 题目描述&#xff1a; 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target …...

周末总结(2024/01/25)

工作 人际关系核心实践&#xff1a; 要学会随时回应别人的善意&#xff0c;执行时间控制在5分钟以内 坚持每天早会打招呼 遇到接不住的话题时拉低自己&#xff0c;抬高别人(无阴阳气息) 朋友圈点赞控制在5min以内&#xff0c;职场社交不要放在5min以外 职场的人际关系在面对利…...

解决日志中 `NOT NULL constraint failed` 异常的完整指南

在开发和运维过程中,日志是我们排查问题的重要工具。然而,当日志中出现类似 NOT NULL constraint failed 的异常时,往往意味着数据库约束与代码逻辑不匹配。本文将详细分析此类问题的原因,并提供完整的解决方案。 © ivwdcwso (ID: u012172506) 问题描述 在同步 AWS …...

线性规划:机器学习中的优化利器

一、线性规划的基本概念 线性规划&#xff08;Linear Programming, LP&#xff09;是运筹学中数学规划的一个重要分支&#xff0c;用于在一组线性不等式的约束条件下&#xff0c;找到线性目标函数的最大值或最小值。其问题可以表述为&#xff1a; 在一组线性约束条件 s.t.&am…...

Flutter子页面向父组件传递数据方法

在 Flutter 中&#xff0c;如果父组件需要调用子组件的方法&#xff0c;可以通过以下几种方式实现。以下是常见的几种方法&#xff1a; 方法 1&#xff1a;使用 GlobalKey 和 State 调用子组件方法 这是最直接的方式&#xff0c;通过 GlobalKey 获取子组件的 State&#xff0c…...

乐鑫 ESP32-C6 通过 Thread 1.4 互操作性认证

乐鑫信息科技 (688018.SH) 很高兴地宣布&#xff0c;ESP32-C6 已经成功通过 Thread 1.4 互操作性认证。这一成就标志着乐鑫在提供先进物联网解决方案之路上又迈进了重要一步。ESP32-C6 在 Thread Group 授权实验室的严格测试中&#xff0c;展现了与最新 Thread 1.4 协议的无缝兼…...

机器学习2 (笔记)(朴素贝叶斯,集成学习,KNN和matlab运用)

朴素贝叶斯模型 贝叶斯定理&#xff1a; 常见类型 算法流程 优缺点 集成学习算法 基本原理 常见方法 KNN&#xff08;聚类模型&#xff09; 算法性质&#xff1a; 核心原理&#xff1a; 算法流程 优缺点 matlab中的运用 朴素贝叶斯模型 朴素贝叶斯模型是基于贝叶斯…...

docker安装elk6.7.1-搜集java日志

docker安装elk6.7.1-搜集java日志 如果对运维课程感兴趣&#xff0c;可以在b站上、A站或csdn上搜索我的账号&#xff1a; 运维实战课程&#xff0c;可以关注我&#xff0c;学习更多免费的运维实战技术视频 0.规划 192.168.171.130 tomcat日志filebeat 192.168.171.131 …...

苍穹外卖-day06

[!IMPORTANT] HttpClient 是什么&#xff1f;它的作用是什么&#xff1f;在微信登录流程中&#xff0c;code 是什么&#xff1f;它的作用是什么&#xff1f;微信登录的具体步骤有哪些&#xff1f;在微信登录流程中&#xff0c;token 的作用是什么&#xff1f;在微信登录中&…...

iic、spi以及uart

何为总线&#xff1f; 连接多个部件的信息传输线&#xff0c;是部件共享的传输介质 总线的作用&#xff1f; 实现数据传输&#xff0c;即模块之间的通信 总线如何分类&#xff1f; 根据总线连接的外设属于内部外设还是外部外设将总线可以分为片内总线和片外总线 可分为数…...

如何高效启动并优化你的Google广告?

在现代数字营销中&#xff0c;Google广告&#xff08;Google Ads&#xff09;已经成为提升品牌曝光、吸引潜在客户和推动销售增长的重要工具。无论你是刚接触广告投放的新手&#xff0c;还是希望优化广告效果的资深营销人员&#xff0c;理解如何有效启动并管理Google广告至关重…...

【Android】布局文件layout.xml文件使用控件属性android:layout_weight使布局较为美观,以RadioButton为例

目录 说明举例 说明 简单来说&#xff0c;android:layout_weight为当前控件按比例分配剩余空间。且单个控件该属性的具体数值不重要&#xff0c;而是多个控件的属性值之比发挥作用&#xff0c;例如有2个控件&#xff0c;各自的android:layout_weight的值设为0.5和0.5&#xff0…...

低代码系统-产品架构案例介绍、简道云(七)

今天分析另外一个零代码、低代码产品-简道云&#xff0c;跟所有低代码产品的架构图一样&#xff0c;高、大、炫、美。 依然是从下至上&#xff0c;从左到右的顺序。 开发层 搭建中心 表单、流程、报表、用户中心&#xff0c;还是这些内容&#xff0c;自定义打印很多平台都有&am…...

RabbitMQ 分布式高可用

文章目录 前言一、持久化与内存管理1、持久化机制2、内存控制1、命令行2、配置文件 3、内存换页4、磁盘控制 二、集群1、Erlang的分布式特性2、RabbitMQ的节点类型2.1、磁盘节点 (Disk Node)2.2、内存节点 (RAM Node) 3、构建集群3.1 普通集群3.2 镜像队列3.3、高可用实现方案3…...