【Linux】生产者消费者模型(阻塞队列与环形队列)和POSIX信号量

文章目录

  • 一、生产者消费者模型
  • 二、基于BlockingQueue的生产者消费者模型
    • 1.BlockQueue.hpp
    • 2.Task.hpp
    • 3.main.cc
  • 三、POSIX信号量
  • 四、基于环形队列的生产消费模型
    • 1.RingQueue.hpp
    • 2.Task.hpp
    • 3.main.cc

一、生产者消费者模型

我们这里举一个例子,来解释生产者消费者模型,我们学生–消费者,供应商–生产者,超市–交易场所,我们买东西只需要关系售货架子上是否有商品即可,没有了商品,超市从供应商进行供货。供应商和供应商不能同时向一个货架进行供货,所以生产者之间是互斥的关系,非消费者和消费不能同时从同一个货架拿商品,所以消费者与消费者之间是互斥的关系,而消费者需要等生产者将商品放到货架之后才能拿取商品,所以生产者和消费者之间是互斥和同步的关系。

生产消费模型:

生产者和生产者之间:互斥关系

消费者和消费者之间:互斥关系

生产者和消费者之间:互斥&&同步

总结:“321”原则:

3种关系:生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者

互斥保证共享资源的安全性]同步)–产品(数据)

种角色:生产者线程,消费者线程

1个交易场所:(一段特定结构的缓冲区

只要我们想写生产消费模型,我们本质工作其实就是维护321原则!

挖掘特点:

1.生产线程和消费线程进行解耦

2支持生产和消费的一段时间的忙闲不均的问题

3.提高效率

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

生产者消费者模型优点

解耦

支持并发

支持忙闲不均

在这里插入图片描述

二、基于BlockingQueue的生产者消费者模型

BlockingQueue

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

在这里插入图片描述

1.BlockQueue.hpp

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>using namespace std;
const int gnum = 5;template <class T>
class BlockQueue
{
public:BlockQueue(const int& maxcap = gnum): _maxcap(maxcap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_pcond, nullptr);pthread_cond_init(&_ccond, nullptr);}void push(const T &in) // 输出型参数:*, // 输入输出型:&{pthread_mutex_lock(&_mutex);// 1. 判断// 细节2: 充当条件判断的语法必须是while,不能用ifwhile (is_full()){// 细节1:pthread_cond_wait这个函数的第二个参数,必须是我们正在使用的互斥锁!// a. pthread_cond_wait: 该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起// b. pthread_cond_wait: 该函数在被唤醒返回的时候,会自动的重新获取你传入的锁pthread_cond_wait(&_pcond, &_mutex);}// 2. 走到这里一定是没有满_q.push(in);// 3. 绝对能保证,阻塞队列里面一定有数据// 细节3:pthread_cond_signal:这个函数,可以放在临界区内部,也可以放在外部pthread_cond_signal(&_ccond);pthread_mutex_unlock(&_mutex);}void pop(T *out){pthread_mutex_lock(&_mutex);// 1. 判断while (is_empty()){pthread_cond_wait(&_ccond, &_mutex);}// 2. 走到这里我们能保证,一定不为空*out = _q.front();_q.pop();// 3. 绝对能保证,阻塞队列里面,至少有一个空的位置!pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}private:bool is_full(){return _q.size() == _maxcap;}bool is_empty(){return _q.empty();}private:queue<T> _q;int _maxcap; // 队列中元素的上限pthread_mutex_t _mutex;pthread_cond_t _pcond; // 生产者对应的条件变量pthread_cond_t _ccond; // 消费者对应的条件变量
};

2.Task.hpp

#pragma once#include <iostream>
#include <string>
#include <functional>class CalTask
{
public:typedef std::function<int(int, int, char)> func_t;// using func_t = std::function<int(int, int, char)>;public:CalTask(){}CalTask(int x, int y, char op, func_t func): _x(x), _y(y), _op(op), _callback(func){}std::string operator()(){int result = _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);return buffer;}private:int _x;int _y;char _op;func_t _callback;
};const std::string oper = "+-*/%";int calculate(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error" << std::endl;return -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error" << std::endl;return -1;}elseresult = x % y;}break;default:std::cerr << "请输入正确的操作符" << std::endl;break;}return result;
}class SaveTask
{
public:using func_t = function<void(const std::string &)>;public:SaveTask(){}SaveTask(const std::string &message, func_t func): _message(message), _func(func){}void operator()(){_func(_message);}~SaveTask(){}private:std::string _message;func_t _func;
};void Save(const std::string &message)
{const std::string target = "./log.txt";FILE *fp = fopen(target.c_str(), "a+");if (fp == nullptr){perror("fopen error");return;}fputs(message.c_str(), fp);fputs("\n", fp);fclose(fp);
}

3.main.cc

#include "BlockQueue.hpp"
#include "Task.hpp"#include <ctime>
#include <unistd.h>
#include <sys/types.h>// C:计算
// S: 存储
template <class C, class S>
class BlockQueues
{
public:BlockQueue<C> *_cbq;BlockQueue<S> *_sbq;
};void *productor(void *args)
{BlockQueue<CalTask> *bq = static_cast<BlockQueues<CalTask, SaveTask> *>(args)->_cbq;while (true){int x = rand() % 10 + 1;int y = rand() % 5;char op = oper[rand() % 5];CalTask t(x, y, op, calculate);bq->push(t);std::cout << "productor thread 生产计算任务: " << t.toTaskString() << std::endl;sleep(1);}return nullptr;
}void *consumer(void *args)
{BlockQueue<CalTask> *bq = static_cast<BlockQueues<CalTask, SaveTask> *>(args)->_cbq;BlockQueue<SaveTask> *save_bq = static_cast<BlockQueues<CalTask, SaveTask> *>(args)->_sbq;while (true){CalTask t;bq->pop(&t);std::string result = t();std::cout << "cal thread,完成计算任务: " << result << " ... done" << std::endl;SaveTask st(result, Save);save_bq->push(st);cout << "cal thread,推送存储任务完成..." << std::endl;sleep(1);}return nullptr;
}void *saver(void *args)
{BlockQueue<SaveTask> *save_bq = static_cast<BlockQueues<CalTask, SaveTask> *>(args)->_sbq;while (true){SaveTask st;save_bq->pop(&st);st();std::cout << "save thread,保存任务完成..." << std::endl;// sleep(1);}return nullptr;
}int main()
{BlockQueues<CalTask, SaveTask> bqs;bqs._cbq = new BlockQueue<CalTask>();bqs._sbq = new BlockQueue<SaveTask>();pthread_t p, c, s;pthread_create(&p, nullptr, productor, &bqs);pthread_create(&c, nullptr, consumer, &bqs);pthread_create(&s, nullptr, saver, &bqs);pthread_join(p, nullptr);pthread_join(c, nullptr);pthread_join(s, nullptr);delete bqs._cbq;delete bqs._sbq;return 0;
}

你创建多线程生产和消费的意义是什么??2.生产消费模型高效在哪里??

可以在生产之前,和消费之后,让线程并行执行

生产者而言,向blockqueue里面放置任务

他的任务从哪里来的呢?它获取任务和构建任务要不要花时间?

消费者而言,从blockqueue里面拿取任务

对于消费者,难道他把任务从任务队列中拿出来就完了吗??消费者拿到任务之后,后续还有没有任务??

三、POSIX信号量

先发现我们之前写的代码的不足的地方

pthread_mutex_lock(&_mutex);
while (is_full())
{pthread_cond_wait(&_pcond, &_mutex);
}
_q.push(in);
pthread_cond_signal(&_ccond);
pthread_mutex_unlock(&_mutex);

1.一个线程,在操作临界资源的时候,必须临界资源是满足条件的!

2.可是,公共资源是否满足生产或者消费条件,我们无法、直接得知【我们不能事前得知【在没有访问之前,无法得知】】

3.只能先加锁,再检测,再操作,再解锁。因为你要检测,本质:也是在访问临界资源!

因为我们在操作临界资源的时候,有可能不就绪但是,我们无法提前得知,所以,只能先加锁,在检测,根据检测结果,决定下一步怎么走!

只要我们对资源进行整体加锁,就默认了,我们对这个资源整体使用。实际情况可能存在:.一份公共资源,但是允许同时访问不同的区域!程序员编码保证不同的线程可以并发访问公共资源的不同区域!

什么是信号量:

a.信号量本质是一把计数器。衡量临界资源中资源数量多少的计数器

b.只要拥有信号量,就在未来一定能够拥有临界资源的一部分,申请信号量的本质:对临界资源中特定小块资源的预订机制

线程要访问临界资源中的某一区域–申请信号量–所有人必须的先看到信号量–信号量本身必须是:公共资源

有可能,我们在访问真正的临界资源之前,我们其实就可以提前知道临界资源的使用情况! ! !

信号量只要申请成功,就一定有你的资源。只要申请失败,就说明条件不就绪,你只能等!!不需要在判断了!

计数器–递减or 递增sem_t sem = 10;

sem–;—申请资源—必须保证操作的原子性— P

sem++;—归还资源—必须保证操作的原子性----V

信号量核心操作:PV原语

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

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

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

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

在这里插入图片描述

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

在这里插入图片描述

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程

生产和消费在什么情况下可能访问同一个位置:

1.空的时候

2.满的时候

3.其他情况,生产者和消费者,根本访问的就是不同的区域!

为了完成环形队列cp问题,我们要做的核心工作是什么

1.你不能超过我

2我不能把你套一个圈以上

3.我们两个什么时候,会站在一起?

信号量是用来衡量临界资源中资源数量的

1.对于生产者而言,看中什么?队列中的剩余空间—空间资源定义十个信号量

2.对于消费者而言,看中的是什么?放入队列中的数据! —数据资源定义一个信号量

生产者而言:

prodocter_sem: 0
// 申请成功,你就可以继续向下运行。
//申请失败,当前执行流,阻塞在申请处
P(producter_sem);
//从事生产活动--把数据放入到队列中
V(comsumer_sem);

消费者而言:

comsumer_sem:10
P(comsumer_sem);
//从事消费活动
v(producter_sem);_

未来,生产和消费的位置我们要想清楚:

1.其实就是队列中的下标、

2一定是两个下标

3.为空或者为满,下标相同

1.RingQueue.hpp

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <semaphore.h>static const int gcap = 5;template <class T>
class RingQueue
{
private:// 等待信号量,会将信号量的值减1void P(sem_t &sem){int n = sem_wait(&sem);assert(n == 0);(void)n;}// 发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。void V(sem_t &sem){int n = sem_post(&sem);assert(n == 0);(void)n;}public:RingQueue(const int &cap = gcap): _queue(cap), _cap(cap){int n = sem_init(&_spaceSem, 0, _cap);assert(n == 0);n = sem_init(&_dataSem, 0, 0);assert(n == 0);_productorStep = _consumerStep = 0;pthread_mutex_init(&_pmutex, nullptr);pthread_mutex_init(&_cmutex, nullptr);}// 生产者void push(const T &in){P(_spaceSem);pthread_mutex_lock(&_pmutex);_queue[_productorStep++] = in;_productorStep %= _cap;pthread_mutex_unlock(&_pmutex);V(_dataSem);}// 消费者void pop(T *out){P(_dataSem);pthread_mutex_lock(&_cmutex);*out = _queue[_consumerStep++];_consumerStep %= _cap;pthread_mutex_unlock(&_cmutex);V(_spaceSem);}~RingQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);pthread_mutex_destroy(&_pmutex);pthread_mutex_destroy(&_cmutex);}private:std::vector<T> _queue;int _cap;sem_t _spaceSem; // 生产者 想生产,看中的是什么资源呢? 空间资源sem_t _dataSem;  // 消费者 想消费,看中的是什么资源呢? 数据资源int _productorStep;int _consumerStep;pthread_mutex_t _pmutex;pthread_mutex_t _cmutex;
};

2.Task.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <functional>class Task
{using func_t = std::function<int(int,int,char)>;// typedef std::function<int(int,int)> func_t;
public:Task(){}Task(int x, int y, char op, func_t func):_x(x), _y(y), _op(op), _callback(func){}std::string operator()(){int result = _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);return buffer;}
private:int _x;int _y;char _op;func_t _callback;
};const std::string oper = "+-*/%";int mymath(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}elseresult = x % y;}break;default:// do nothingbreak;}return result;
}

3.main.cc

#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>std::string SelfName()
{char name[128];snprintf(name, sizeof(name), "thread[0x%x]", pthread_self());return name;
}void *ProductorRoutine(void *rq)
{// RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);while (true){// version1// int data = rand() % 10 + 1;// ringqueue->Push(data);// std::cout << "生产完成,生产的数据是:" << data << std::endl;// version2// 构建or获取任务 --- 这个是要花时间的!int x = rand() % 10;int y = rand() % 5;char op = oper[rand() % oper.size()];Task t(x, y, op, mymath);// 生产任务ringqueue->push(t);// 输出提示std::cout << SelfName() << ", 生产者派发了一个任务: " << t.toTaskString() << std::endl;// sleep(1);}
}void *ConsumerRoutine(void *rq)
{// RingQueue<int> *ringqueue = static_cast<RingQueue<int> *>(rq);RingQueue<Task> *ringqueue = static_cast<RingQueue<Task> *>(rq);while (true){// version1//  int data;//  ringqueue->Pop(&data);//  std::cout << "消费完成,消费的数据是:" << data << std::endl;//  sleep(1);// version2Task t;// 消费任务ringqueue->pop(&t);std::string result = t(); // 消费也是要花时间的!std::cout << SelfName() << ", 消费者消费了一个任务: " << result << std::endl;// sleep(1);}
}int main()
{srand((unsigned int)time(nullptr) ^ getpid() ^ pthread_self() ^ 0x71727374);// RingQueue<int> *rq = new RingQueue<int>();RingQueue<Task> *rq = new RingQueue<Task>();// 单生产,单消费,多生产,多消费 --> 只要保证,最终进入临界区的是一个生产,一个消费就行!// 多生产,多消费的意义??pthread_t p[4], c[8];for (int i = 0; i < 4; i++)pthread_create(p + i, nullptr, ProductorRoutine, rq);for (int i = 0; i < 8; i++)pthread_create(c + i, nullptr, ConsumerRoutine, rq);for (int i = 0; i < 4; i++)pthread_join(p[i], nullptr);for (int i = 0; i < 8; i++)pthread_join(c[i], nullptr);delete rq;return 0;
}

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

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

相关文章

【数据结构和算法】寻找数组的中心下标

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 前缀和的解题模板 2.1.1 最长递增子序列长度 2.1.2 寻找数组中第 k 大的元素 2.1.3 最长公共子序列…

雷盛红酒和云仓酒庄的优势

多国家采购、多葡萄酒品种、多价位区间的全系列整体品牌形式的雷盛&#xff08;LEESON&#xff09;红酒云仓酒庄&#xff0c;具有以下优势&#xff1a; 1.明星代言。雷盛&#xff08;LEESON&#xff09;品牌系列葡萄酒有幸邀请著名导演张纪中先生担任品牌代言人&#xff0c;为…

什么是天线OTA,怎么通过OTA数据评估产品射频环境情况

1.1 验证项目 产品的器件布局、走线是否合理、电源输入输出设计、纹波控制&#xff0c;铺地回流设计等是否合理. 通过验证产品的天线OTA_TIS项目来作为评估当前的设计是否合理之一&#xff0c;重点验证低频部分&#xff0c;如Band8段数据. 1.2 什么是天线OTA 是指某无线产品…

Vue3使用的Compostion Api和Vue2使用的Options Api有什么不同?

我们介绍Compostion Api和Options Api的区别之前&#xff0c;先来说一下为什么会推出来Composition Api&#xff0c;解决了什么问题&#xff1f; Vue2开发项目使用Options Api存在的问题 代码的可读性和维护性随着组件的变大业务的增多而变得差代码的共享和重用性存在缺点不支…

【Linux】进程查看|fork函数|进程状态

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

H.264宏块(Macroblock)概念(运动估计、变换编码、环路滤波)

参考文章&#xff1a;音视频高手课系列5-h264编码基础(宏块原理) 参考文章&#xff1a;切片slice与宏块&#xff0c;运动矢量 文章目录 使用videoEye分析视频宏块示例H.264宏块概念1. 宏块的定义2. 运动估计3. 变换编码4. 环路滤波5. 注意&#xff1a;宏块的概念既适用于帧内编…

基于Java Swing的图书管理系统

一、项目总体架构 本项目基于Java Swing框架&#xff0c;数据库采用的是MySQL。项目文件夹如下&#xff1a; 二、项目截图 1.登录和注册界面 2.用户界面 3.管理员管理图书类别 4.管理员管理书籍 5.管理员管理用户 项目总体包括源代码和课程论文&#xff0c;需要源码的…

通过MobaXterm远程连接Anolis

目录 前言&#xff1a; 一.设置ip 二.远程连接 前言&#xff1a; 小编已经阐述了如何安装Anolis系统&#xff0c;如果有不了解的小伙伴可以查看这一篇博客Anolis安装 这篇博客将会讲述如何远程连接Anolis系统。各位看官拿好板凳&#xff01; 一.设置ip 打开网卡所在位…

西门子PLC通过PROFINET协议与多功能电表通讯

西门子PLC通过PROFINET协议与多功能电表通讯 项目要求 西门子S71200PLC需要通过PROFINET协议和多功能电表通讯&#xff0c;读取线电压、相电压、线电流、相电流、有功功率、无功功率等参数。 项目实施 采用网关NET90-PN-MBT&#xff08;以下简称“网关”&#xff09;&#…

怎么提取视频中的背景音乐?

当我们在刷视频的时候&#xff0c;有时候听到一个背景音乐很好听&#xff0c;但是又不知道歌名&#xff0c;比如英语歌&#xff0c;这个时候我们很难找到这首歌&#xff0c;相信有很多朋友会遇到这样的问题&#xff0c;不知道怎么弄&#xff0c;下面小编给大家推荐一些方法帮助…

Pytorch从零开始实战14

Pytorch从零开始实战——DenseNet SENet算法实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——DenseNet SENet算法实战环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#x…

亿赛通电子文档安全管理系统 linkfilterservice 未授权漏洞

产品简介 亿赛通电子文档安全管理系统&#xff0c;&#xff08;简称&#xff1a;CDG&#xff09;是一款电子文档安全加密软件&#xff0c;该系统利用驱动层透明加密技术&#xff0c;通过对电子文档的加密保护&#xff0c;防止内部员工泄密和外部人员非法窃取企业核心重要数据资…

Spring企业开发核心框架

文章目录 Spring企业开发核心框架一、框架前言1. 总体技术体系2. 框架概念和理解 二、Spring Framework简介1. Spring 和 SpringFramework2. SpringFramework主要功能模块3. SpringFramework 主要优势 三、Spring IoC 容器概念1. 组件和组件管理概念2. Spring IoC容器和容器实现…

ALS-运动系统解构

角色握持 角色蓝图&#xff1a;将物体绑在手上 动作蓝图&#xff1a; 将握持动画截取一帧&#xff08;explicit time时间写好&#xff09; 角色替换 在原人物模型下面加一个骨骼体&#xff08;先不用添加模型&#xff09;&#xff0c;重命名为bodymesh AI使用流程 新建一…

品牌如何在线上打造“社交货币”?媒介盒子揭秘

品牌的社交货币&#xff0c;是品牌与消费者的共识身份铸造器。竹筒奶茶、Keep奖牌这类的实体产品作为社交货币&#xff0c;每每能够引爆社交平台&#xff0c;那么品牌能否通过线上平台打造“社交货币”呢&#xff1f;接下来就让媒介盒子和大家聊聊。 一、社交货币是什么 社交货…

6.Nacos

1.单机部署 1.1 官网 https://nacos.io/zh-cn/index.html https://github.com/alibaba/Nacos 1.2.版本说明 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E 1.3.下载地址 https://github.com/alibaba/nacos/releases/tag/2.2.…

小区跑腿服务

社区跑腿服务是指在社区范围内为居民提供各种便利的服务&#xff0c;包括购物代劳、快递代取、家政服务等。 这种服务的出现&#xff0c;满足了居民生活中诸多需求&#xff0c;受到了广泛的欢迎和认可。 首先&#xff0c;社区跑腿服务方便了居民的日常生活。 居民无需亲自前…

克魔助手工具下载、注册和登录指南

下载安装克魔助手 摘要 本文介绍了如何下载安装克魔助手工具&#xff0c;以及注册和登录流程。通过简单的步骤&#xff0c;用户可以轻松获取并使用该工具&#xff0c;为后续的手机应用管理操作做好准备。 引言 克魔助手是一款免费的手机管理工具&#xff0c;通过该工具用户…

文章解读与完整程序——《考虑“源-荷-储”协同互动的主动配电网优化调度研究》

摘要&#xff1a;伴随智能电网的建设和清洁能源的开发利用,配电网中的负荷类型呈现多元化发展,分布式电源、可控负荷、储能等资源的增加让单向潮流的传统配电网逐渐向双向潮流的主动配电网结构转变。在能源结构转变的同时,清洁能源自身的随机性和波动性给配电网带来了更大的调峰…

2023.12.25 关于 Redis 数据类型 Hash 常用命令、内部编码、应用场景

目录 Hash 数据类型 Hash 操作命令 HSET HGET HEXISTS HDEL HKEYS HVALS HGETALL HMGET HLEN HSETNX HINCRBY HINCRBYFLOAT HSTRLEN Hash 编码方式 理解什么是压缩 Hash 实际应用 Cache 缓存 Hash 数据类型 整体上来说 Redis 是键值对结构&#xff0c;其中 …