【Linux】线程池
目录
线程池
日志
线程池
在程序中,会预先创建一批线程,在内部会有一个叫任务队列task_queue的东西,未来如果有任务要处理,就把任务push到任务队列里,然后自动有线程去任务队列里拿任务并处理,如果没有任务,所有线程会自动休眠,一旦有任务到来,就唤醒某一个线程帮我们处理这个任务。这种提前把线程创建出来,当任务到来时,指定一个线程去帮我们处理任务,这种就叫做线程池。实际上,线程池的本质就是一个生产消费模型。线程池的基本结构如下:
template<class T>
class ThreadPool
{ThreadPool(int thread_num = gdefaultnum):_pthread_num(thread_num),_isrunning(false){}void Init(){}void Start(){}void Stop(){}void Equeue(const T& in){}~ThreadPool(){}
private:int _pthread_num;std::vector<std::thread> _threads;std::queue<T> _task_queue;bool _isrunning;
};
其中,_pthread_num是要创建的线程数,_threads是对线程进行管理的数组,_task_queue是任务队列,使用模版的参数T。另外,我们还需要使用_isrunning用来控制线程池是否启动。对于ThreadPool的成员变量,我们暂时设置成这么多。后面需要了再加。
在构造函数中,我们唯一需要暴露出去的就是_pthread_num,用于让上层用户决定创建多少线程,我们也需要设置一个缺省值,然后_isrunning设置为false。
static const int gdefaultnum = 5;
ThreadPool(int thread_num = gdefaultnum): _thread_num(thread_num),_isrunning(false)
{}
接下来,我们的线程池初始化接口Init如下,使用test函数去初始化每个线程,也就是每个线程都去执行test函数,test暂时设置为void test():
void Init()
{for (int i = 0; i < _thread_num; i++){_threads.emplace_back(test);}
}
void test()
{while(true){std::cout << "hello world" << std::endl;sleep(1);}
}
运行结果如下,可以看到除了一个主线程,还有创建的5个新线程:
接下来,我们就需要考虑向任务队列中push任务。由于所有线程都要访问_task_queue队列,所以它是共享资源,因此要加锁。一旦把一个任务推送进来,就要唤醒一个进程(如果有休眠的线程),因此,还要加一个条件变量_cond:
private:int _thread_num;std::vector<std::thread> _threads;std::queue<T> _task_queue;bool _isrunning;pthread_mutex_t _mutex;pthread_cond_t _cond;
入队列前需要加锁,出队列后需要解锁,因此,可以对加锁解锁做一下封装,并且在构造函数中对锁和条件变量做一下初始化,并在析构函数中销毁一下:
ThreadPool(int thread_num = gdefaultnum): _thread_num(thread_num),_isrunning(false)
{pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);
}
~ThreadPool()
{for (auto& thread : _threads){if (thread.joinable())thread.join();}pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);
}
void LockQueue()
{pthread_mutex_lock(&_mutex);
}
void UnLockQueue()
{pthread_thread_unlock(&_mutex);
}
因此,在入队列前后要进行加锁和解锁,使用LockQueue和UnLockQueue,在中间直接向任务队列中push任务。push完任务之后,只要有线程在休眠,就要先去唤醒一个线程(执行WakeUp函数,是对条件变量的封装),如果没有线程在休眠,就没必要唤醒线程了,所以,为了判断有没有线程在休眠,还要维护一个休眠线程数这样的一个计数器成员变量_sleep_thread_num,并在初始化列表中初始化为0,:
//唤醒一个线程
void WakeUp()
{pthread_cond_signal(&_cond);
}
//任务入队列
void Equeue(const T& in)
{LockQueue();_task_queue.push(in);if(_sleep_thread_num > 0)WakeUp();UnLockQueue();
}
那每个线程应该做什么呢?应该去处理我们push的任务,我们让每个线程都去执行HandlerTask函数,每个线程都不应该退出,所以HandlerTask里应该是一个死循环。每个进程都要从任务队列中取任务,但是不排除主线程正在往里面push任务,所以_task_queue就是一个临界资源,所以,每个线程都要竞争式地去申请锁LockQueue,把队列锁住,未来再UnQueueLock。所以,线程从任务队列中取任务时,在这里就保证了线程安全。如果任务队列为空(封装IsEmpty函数,判断任务队列是否为空),那就让线程去指定的条件变量下去休眠(封装Sleep函数,去_cond条件变量下去休眠,同时释放掉持有的锁);如果任务队列不为空||线程被唤醒,就从任务队列中拿出来一个任务t,并处理这个任务,我们约定,所有的任务都可以使用t()来执行,注意,此处不用/不能在临界区中处理,因为这个任务被pop出来后,只属于当前这个线程,而处理这个任务和临界资源的访问是两码事,如果放在加锁解锁之间处理,不仅浪费时间,而且所有线程处理任务都变成串行的了。
// 任务处理函数,每个线程执行此函数来处理任务
void HandlerTask()
{while (true){//取任务LockQueue();//任务队列为空,就去指定的条件变量下去休眠while(IsEmpty()) //使用while,防止伪唤醒{Sleep(); //会把持有的锁释放掉}//有任务 || 被唤醒T t = _task_queue.front();_task_queue.pop();UnLockQueue();//处理任务t(); // 处理任务,此处不能/不用在临界区处理}
}
此时问题来了,我们刚才让每个线程执行的是test这样一个简单的函数,那如何让每个线程去执行HandlerTask(类内方法)呢?我们直接把HandlerTask作为参数在Init的时候传给每个线程吗?这样线程一启动就可以自动执行这个类内方法了,我们先来编译一下:
这个报错是为啥呢?因为HandlerTask参数中是有this指针的,此时我们并不能把HandlerTask定义为static,因为访问了类内的非静态成员变量和函数,为了解决这样的问题,在C++中,可以使用std::bind函数将HandlerTask的第一个参数绑定为this指针,这样做最大的好处,就是在面向对象的基础之上,让一个类去调用另一个类内部的方法(需要包functional头文件)。现在就可以编译通过了,我们在main函数中项线程池中push我们预先准备好的任务:
class Task
{
public:Task(){}Task(int x, int y):_x(x),_y(y){}void Excute(){_result = _x + _y;}void operator()(){Excute();}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;
};
#include "ThreadPool.hpp"
#include "Task.hpp"
#include <memory>int main()
{std::unique_ptr<ThreadPool<Task>> tp = std::make_unique<ThreadPool<Task>>();tp->Init();tp->Start();while(true){//不断向线程池推送任务sleep(1);Task t(1, 1);tp->Equeue(t);}return 0;
}
我们编译运行,发现没反应,实际上我们在HandlerTask中的Sleep之前,需要让_sleep_thread_num++,在Sleep之后,_sleep_thread_num--。
我们编译运行一下:
我们发现一个问题,我怎么知道这是哪个线程打印出来的呢?所以,我们还需要给HandlerTask传递一个参数--线程名,并使用std::bind使用一个占位符,用于传name。
运行结果如下:
但是,到现在还没有结束,现在只是把线程池初始化并启动, 并没有写怎么结束线程池。万一我只想让线程池处理10个任务,处理完就要Stop,Stop之后就应该看到线程数由6个变成1个,所以,我们应该如何让线程池中的所有线程退出呢?这就和我们之前的_isrunning这个成员变量有关系了,默认情况下_isrunning是false,在InitAndStart中我们需要将_isrunning设置为true。我们来想一下,某一个时间,线程池中的线程有的在处理任务,有的在休眠,有的在获取任务,反正每个线程所做的工作都可能是不一样的,但有一个基本条件就是,如果①任务队列里没有任务了&&②线程池的_isrunning是false状态,就应该让所有线程退出;否则,①只要任务队列中只要有任务,必须把任务处理完,线程池要退出,处理完再退出;②如果任务没有处理完,并且线程池没退出,那就处理完就休眠。所以我们发现还要对线程池做状态方面的判断。所以在休眠Sleep那个while的判断条件中,要判断任务队列中是否为空和_isrunning是不是false,如果任务队列为空&&线程池不退出,就去指定的条件变量下去休眠,否则就不应该休眠。当退出while循环后,只有当任务队列为空&&线程要退出,此时才释放锁并break,否则如果有任务还没被处理完,否则线程就不应该退!如果_isrunning是false,尽管还有任务没处理完,但是再也不会进while循环去休眠,直到把任务处理完后就退出!所以,线程池的Stop函数很简单,直接让_isrunning=false即可。
但是执行完Stop中的_isrunning=false时,有可能其他线程都在while循环中的_cond下休眠,那就无法唤醒这些线程,线程也就不会退出了。所以在Stop中,除了将_isrunning设置为false,还唤醒所有线程WakeUpAll,
void WakeUpAll()
{pthread_cond_broadcast(&_cond);
}
另外,在_isrunning=false前后最好也加上加解锁,这样更安全一些,Stop函数如下:
现在还有一个小问题是,如果线程池已经退出,但是上层一直有一个“不长眼”的线程一直往线程池中push任务,导致线程池一直得处理任务而不能退出,这不就有问题了吗??因此,在任务入队列时,要先判断线程池要处于运行状态(_isrunning==true),才可以将任务入队列。
void Equeue(const T& in)
{LockQueue();//向线程池push任务时,需要先判断线程池是否在运行,在运行才可以push任务if(_isrunning){_task_queue.push(in);if(_sleep_thread_num > 0)WakeUp();}UnLockQueue();
}
所以,综合来看,当线程池Stop后,所有的线程都不会去休眠而去处理任务,把这个门关掉了;同时,通过Equeue这个函数中的if(_isrunning)判断,就不会让新的任务入队列,把这个门也关掉了。所以,我们把门关上,可以把存量的任务处理完,让线程去退出!
我们来看一下运行情况,使用 while :; do ps -aL; sleep 1; done 这个监控脚本来查看进程状态:
至此,进程池的代码完成,我们还可以将进程池改成单例模式,单例模式的线程池的代码附录在本文最后,同时代码Gitee链接敬上 线程池完整版代码C++。但是在上面的打印中,我们看起来不太明确,而且想在代码中加打印信息也要加std::cout这样的语句,看起来不直观,所以,我们之后可以使用日志来完成信息的打印查看!
日志
日志是软件运行的记录信息,可以向显示器或文件中打印,并且有特定的格式,我们希望的日志格式是:
[日志等级][pid][filename][filenumber][time] 日志内容(支持可变参数)
其中,日志等级按照分钟程度,分为DEBUG、INFO、WARNING、ERROR、FATAL。
创建一个Log.hpp文件,创建logmessage和Log两个类:
class logmessage
{
public:std::string _level;pid_t _id;std::string _filename;int _filenumber;std::string _curr_time;std::string _message_info;
};
在main函数中,使用日志时,需要创建Log对象,然后使用logMessage函数加载日志信息,设计一个LevelToString函数,
std::string LevelToString(int level)
{switch(level){case DEBUG:return "DEBUG";case INFO:return "INFO";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "UNKNOWN";}
}
再设计GetCurrentTime函数,在这个函数中,首先获取当前的时间戳,然后使用localtime函数将时间戳转为一个包含时间属性的结构体,
std::string GetCurrentTime()
{time_t now = time(nullptr);struct tm* curr_time = localtime(&now);char buffer[128];snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %2d:%2d:%2d", 1900+curr_time->tm_year,1+curr_time->tm_mon,\curr_time->tm_mday,\curr_time->tm_hour,\curr_time->tm_min,\curr_time->tm_sec);return buffer;
}
下一步就是要提取logMessage中的可变参数,为了提取可变参数,在C语言中提供了vsnprintf(需要包含stdarg.h),
这里的ap指向可变参数部分,通过vsprintf按照format格式得到可变部分,并放到log_info中。然后就需要把日志打印出来:
但是,在main函数使用打印日志时,真的要自己手动传入文件名以及行号吗?这未免太low了。其实,C语言中存在预处理符__FILE__和__LINE__,可以得到文件名和行号。
但是,这样写也有点low,能不能把这两个预处理符隐藏起来,另外,也不想使用log.logMessage这种方式了,为了解决这样的问题,我们可以使用宏替换,并使用宏支持可变参数。
#define LOG(Level, Format, ...) do{lg.logMessage(__FILE__, __LINE__, Level, Format, __VA_ARGS__);}while(0)//宏支持可变参数#define EnableScreen() do{lg.Enable(SCREEN_TYPE);}while(0)#define EnableFile() do{lg.Enable(FILE_TYPE);}while(0)
在使用LOG时,有可能不需要可变参数,因此,可以这样修改:
这表示,如果有就使用可变参数部分,如果没有,就把其之前的“,”去掉。
【附录】
-------------------------------------------------线程池代码完成版-------------------------------------------------------
目录结构
Makefile
ThreadPool:main.ccg++ -o $@ $^ -std=c++14
.PHONY:clean
clean:rm -rf ThreadPool
main.cc
#include "ThreadPool.hpp"
#include "Task.hpp"
#include <memory>int main()
{int cnt = 10;while(cnt){//不断向线程池推送任务Task t(1, 1);ThreadPool<Task>::GetInstance()->Equeue(t);sleep(1);std::cout << "cnt: " << cnt-- << std::endl;}ThreadPool<Task>::GetInstance()->Stop();std::cout << "thread pool stop" << std::endl;sleep(10); //此时应该看到线程由6个变成一个return 0;
}
ThreadPool.hpp
#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <functional>
#include <condition_variable>
#include <functional>static const int gdefaultnum = 5;using func_t = std::function<void(std::string)>;void test()
{while(true){std::cout << "hello world" << std::endl;sleep(1);}
}template<class T>
class ThreadPool
{
private:ThreadPool(int thread_num = gdefaultnum): _thread_num(thread_num),_isrunning(false){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}ThreadPool(const ThreadPool<T>&) = delete;ThreadPool<T>& operator=(const ThreadPool<T>&) = delete;bool IsEmpty(){return _task_queue.empty();}// 任务处理函数,每个线程执行此函数来处理任务void HandlerTask(const std::string& name){while (true){//取任务LockQueue();// 如果任务队列为空&&线程池不退出,就去指定的条件变量下去休眠while(IsEmpty() && _isrunning) //使用while,防止伪唤醒{_sleep_thread_num++;Sleep(); //会把持有的锁释放掉_sleep_thread_num--;}//判定一种情况,任务队列为空&&线程池要退出了if(IsEmpty() && !_isrunning){std::cout << name << " quit" << std::endl;UnLockQueue();break;}//有任务 || 被唤醒T t = _task_queue.front();_task_queue.pop();UnLockQueue();//处理任务t(); // 处理任务,此处不能/不用在临界区处理std::cout << name << ":" << t.result() << std::endl;}}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void WakeUp(){pthread_cond_signal(&_cond);}void WakeUpAll(){pthread_cond_broadcast(&_cond);}void Sleep(){pthread_cond_wait(&_cond, &_mutex);}void InitAndStart(){_isrunning = true;for (int i = 0; i < _thread_num; i++){ std::string name = "thread-" + std::to_string(i+1);_threads.emplace_back(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name);}}
public:static ThreadPool<T>* GetInstance(){if(_tp == nullptr){std::lock_guard<std::mutex> lock(_sig_mutex);if(_tp == nullptr){std::cout << "create threadpool\n";_tp = new ThreadPool<T>();_tp->InitAndStart();}else{std::cout << "get threadpool\n";}}return _tp;} void Stop(){LockQueue();_isrunning = false; WakeUpAll();UnLockQueue(); }void Equeue(const T& in){LockQueue();//向线程池push任务时,需要先判断线程池是否在运行,在运行才可以push任务if(_isrunning){_task_queue.push(in);if(_sleep_thread_num > 0)WakeUp();}UnLockQueue();}~ThreadPool(){for (auto& thread : _threads){if (thread.joinable())thread.join();}pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}
private:int _thread_num;std::vector<std::thread> _threads;std::queue<T> _task_queue;bool _isrunning;int _sleep_thread_num; //休眠线程数pthread_mutex_t _mutex;pthread_cond_t _cond;//单例模式static ThreadPool<T>* _tp;static std::mutex _sig_mutex;
};template<class T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;
template<class T>
std::mutex ThreadPool<T>::_sig_mutex;
Task.hpp
#include <iostream>
#include <functional>class Task
{
public:Task(){}Task(int x, int y):_x(x),_y(y){}void Excute(){_result = _x + _y;}void operator()(){Excute();}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;
};
相关文章:
【Linux】线程池
目录 线程池 日志 线程池 在程序中,会预先创建一批线程,在内部会有一个叫任务队列task_queue的东西,未来如果有任务要处理,就把任务push到任务队列里,然后自动有线程去任务队列里拿任务并处理,如果没有任…...
【11408】考研英语长难句解析策略:三步断开与简化法,快速提升阅读得分
2025.04.01 英语断开长难句分析主谓心得 简化长难句心得 英语 断开长难句 在一些长难句中,有时从句的连词会被省略,且没有标点将其隔开,此时就无法通过标点和连接词来断开长难句。那么我们只能够通过分析主谓来断开长难句。 分析主谓 谓语…...
Spring Cloud 2023.x安全升级:OAuth2.1与JWT动态轮换实战
引言:当安全遇上云原生,零停机密钥轮换成为刚需 在微服务架构中,OAuth2.1与JWT已成为身份验证的黄金标准,但传统方案存在两大痛点: 密钥轮换风险:手动替换JWT密钥需重启服务,导致短暂鉴权中断&…...
Vue3.5 企业级管理系统实战(十二):组件尺寸及多语言实现
1 组件尺寸切换 1.1 用 Pinia 进行 Size 的持久化存储 首先,在 src/plugins/element.ts 中增加 size 类型,代码如下: //src/plugins/element.ts import type { App } from "vue";import { ElMessage, ElNotification, ElMessageBo…...
15:00开始面试,15:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到8月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
Java学习路线 - 第三阶段笔记
Java学习路线 - 第三阶段笔记 Java高级特性(2-3个月) 1. 集合框架深入 1.1 List详解 ArrayList:基于动态数组实现,随机访问高效,插入删除效率低LinkedList:基于双向链表实现,插入删除高效&a…...
【无标题】Scala函数基础
函数和方法的区别 1) 核心概念 (1) 为完成某一功能的程序语句的集合,称为函数。 (2) 类中的函数称之方法。 2) 案例实操 (1) Scala 语言可以在任何的语法结构中声明…...
微信登录、商品浏览前瞻
一.业务效果 二.所需技术...
自动化工作流工具的综合对比与推荐
最近收到很多朋友私信我说:“刷短视频的时候,总是刷到自动化工作流的工具,有好多直播间都在宣传,不知道哪款工具好”。我花了点时间,做了一下测试,大家可以参考一下,以下内容: 以下…...
可实现黑屏与蓝屏反应的屏幕隐私保护软件分享
软件介绍 在信息安全备受关注的当下,一款能够有效保护屏幕隐私的软件 —— 防窥助手,悄然问世。它由吾爱的 遗憾迟香精心开发,为用户的屏幕隐私防护带来全新体验。 独特原理,精准守护 防窥助手的运行原理相当巧妙,它…...
PERL开发环境搭建>>Windows,Linux,Mac OS
特点 简单 快速 perl解释器直接对源代码程序解释执行,是一个解释性的语言, 不需要编译器和链接器来运行代码>>速度快 灵活 借鉴了C/C, Basic, Pascal, awk, sed等多种语言, 定位于实用性语言,既具备了脚本语言的所有功能,也添加了高级语言功能 开源.免费 没有&qu…...
【实战】渗透测试下的传输命令
目录 bitsadmin certutil curl ftp js nc perl php py scp vbs wget WindowsDefender bitsadmin 不支持https、ftp协议,php python带的服务器会出错 >bitsadmin /transfer n http://192.168.1.192/Client.exe e:\1.exe >bitsadmin /rawreturn /…...
JSON 基础知识(一)
第一部分:JSON 基础知识 📢 快速掌握 JSON!文章 视频双管齐下 🚀 如果你觉得阅读文章太慢,或者更喜欢 边看边学 的方式,不妨直接观看我录制的 JSON 课程视频!🎬 视频里会用更直观…...
nodejs:midi-writer-js 将基金净值数据转换为 midi 文件
开放式基金是没有公布每日交易量的。 /funds/data/660008.csv 文件开头: date,jz,ljjz 2016-01-04,1.1141,1.1141 2016-01-05,1.1161,1.1161 2016-01-06,1.1350,1.1350 这是一个将开放式基金数据转换为 MIDI音乐的 js 程序示例。该程序将基金净值映射为 MIDI音符的…...
从零实现Json-Rpc框架】- 项目实现 - 服务端registrydiscovery实现
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
自适应二值化与形态学变换在图像颜色识别与替换中的应用解析
目录 前言一、自适应二值化1.1 取均值 ADAPTIVE_THRESH_MEAN_C1.2 高斯加权求和 ADAPTIVE_THRESH_GAUSSIAN_C1.2.1 一维高斯分布1.2.2 二维高速分布1.2.3 二维高斯分布权重计算规则 1.2.3.1 用户设置了σ1.2.3.2 用户没有设置σ1.3 代码二、形态学变换2.1 核 2.2 腐蚀2.3 膨胀…...
JsonCpp 处理 JSON(现代 C++ 方案)(三)
第三部分:JsonCpp 处理 JSON(现代 C++ 方案) 📢 快速掌握 JSON!文章 + 视频双管齐下 🚀 如果你觉得阅读文章太慢,或者更喜欢 边看边学 的方式,不妨直接观看我录制的 JsonCpp 课程视频!🎬 视频里会用更直观的方式讲解 JsonCpp 的核心概念、实战技巧,并配有动手演…...
flutter 曲线学习 使用第三方插件实现左右滑动
flutter 曲线的使用 实现左右滑动 TemperatureChartPage() TemperatureChartPage2() – 不太完善 方法 ChartDrawPage import package:doluyo/dly_package/widget/dly_widget.dart; import package:fl_chart/fl_chart.dart; import package:flutter/material.dart; impor…...
【WRF工具】GIS4WRF详细介绍:配置 WPS/WRF
【WRF工具】GIS4WRF详细介绍 QGIS-GIS4WRF安装(Installation)安装 QGIS安装 GIS4WRF GIS4WRF 配置(Configuration)一、如何进入配置界面二、可配置内容1️⃣ 设置工作目录2️⃣ 与 WPS/WRF 集成3️⃣ 与 NCAR 数据档案集成 参考 GIS4WRF 是一个在 QGIS 中…...
【自用记录】本地关联GitHub以及遇到的问题
最近终于又想起GitHub,想上传代码和项目到仓库里。 由于很早之前有在本地连接过GitHub(但没怎么用),现在需要重新搞起(操作忘得差不多)。 在看教程实操的过程中遇到了一些小问题,遂记录一下。 前…...
小程序中跨页面组件共享数据的实现方法与对比
小程序中跨页面/组件共享数据的实现方法与对比 在小程序开发中,实现不同页面或组件之间的数据共享是常见需求。以下是几种主要实现方式的详细总结与对比分析: 一、常用数据共享方法 全局变量(getApp())、本地缓存(w…...
ngx_http_core_merge_srv_conf
定义在 src\http\ngx_http_core_module.c static char * ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_core_srv_conf_t *prev parent;ngx_http_core_srv_conf_t *conf child;ngx_str_t name;ngx_http_server_name_t…...
如何在中科方德llinux系统上离线安装salt-minion
1,我的系统是什么 国产操作系统 中科方德 NFSChina Server release 4.0.240701 (RTM4-G320) 2,首先准备好两个安装包 salt-minion-2015.8.8-2.el7.noarch.rpm和salt-2015.8.8-2.el7.noarch.rpm 后者这个是前者的依赖项。 所以先安装salt-2015.8.8-2.e…...
RAG系统实战:当检索为空时,如何实现生成模块的优雅降级(Fallback)?
目录 RAG系统实战:当检索为空时,如何实现生成模块的优雅降级(Fallback)? 一、为什么需要优雅降级(Fallback)? 二、常用的优雅降级策略 策略一:预设后备提示࿰…...
输电线路航空标志球:低空飞行的安全路标 / 恒峰智慧科技
在现代社会,随着航空业的快速发展,低空飞行活动日益频繁。为了确保飞行安全,避免飞机与高压电线等障碍物发生碰撞,输电线路航空标志球应运而生。这种装置被广泛应用于高压输电线路上,尤其是超高压和跨江输电线…...
【SPP】蓝牙 SDP 协议在SPP中的互操作性解析
在蓝牙通信体系中,服务发现协议(SDP, Service Discovery Protocol)扮演着 "服务目录" 的核心角色。对于串口通信协议(SPP, Serial Port Profile)而言,SDP 服务记录是设备间建立串口连接的基础&am…...
本地部署vanna ai+通过http请求调用vanna
本地部署vanna ai ① 准备python环境,推荐最新的python12、13版本 ② 安装vanna库 我这里安装的python环境是python312 进入目录python312/Scripts,在该目录下的命令行窗口中输入以下命令:pip jinstall vanna pip install vanna③ 配置向…...
seq2seq
理解 transformer 中的 encoder decoder 详细的 transformer 教程见:【极速版 – 大模型入门到进阶】Transformer 文章目录 🌊 Encoder: 给一排向量输出另外一排向量🌊 Encoder vs. Decoder: multi-head attention vs. masked multi-head at…...
C++ ---- 虚继承
一、什么是虚继承 虚继承就是子类中只有一份间接父类的数据。用于解决多继承中的父类为非虚继承时出现的二义性问题,即菱形继承问题。继承方式需要加上virtual关键字。 二、虚继承的特性 以菱形继承为例: 1.不使用虚继承 根据输出的大小和关系图&…...
COMSOL多层圆片随机堆积三维模型
构建多层圆片随机堆积三维模型可用于材料、化工、土木、生物医学等多领域的研究,如复合材料设计、催化剂载体、颗粒物堆积研究等。本案例介绍在COMSOL内建立三维圆片堆积模型。 三维圆片堆积模型可采用CAD纤维密堆积3D插件建立,参数设置如图所示&#…...
PHP 开发API接口签名验证
就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床。所谓man-in-the-middle攻击简单讲就是指恶意的黑客可以在客户端和服务器端的明文通信通道上做手 脚&#x…...
Web开发-JavaEE应用ORM框架SQL预编译JDBCMyBatisHibernateMaven
知识点: 0、安全开发-JavaEE-构建工具-Maven 1、安全开发-JavaEE-ORM框架-JDBC 2、安全开发-JavaEE-ORM框架-Mybatis 3、安全开发-JavaEE-ORM框架-Hibernate 4、安全开发-JavaEE-ORM框架-SQL注入&预编译 一、演示案例-WEB开发-JavaEE-构建工具-Maven IDEA配置m…...
软考-数据库系统工程师第四版pdf
软考-数据库系统工程师第四版pdf git中的文件相对没有那么清楚,网盘的有高清版 github下载 这里我给出仓库地址 链接: https://github.com/yaodada123/ruankao-pdf https://github.com/yaodada123/ruankao-pdf gitee下载 https://gitee.com/yao-hengchao/ruank…...
扫描仪+文档pdf编辑器+pdf格式转换器
小扫描仪是一款集“扫描仪文档pdf编辑器pdf格式转换器”于一体的多功能扫描软件,软件功能丰富,而且目前是免费,功能包括扫描、编辑、转换三部分。 扫描:扫描的功能包括文档扫描、身份证扫描、护照扫描、书籍扫描、OCR和二维码。 扫…...
【stm32--HAL库DMA+USART+空闲中断不定长收发数据】
串口通信-Hal库实现不定长度收发,DMAUSART DMA串口STM32CUBEMX配置(工程创建)基础配置时钟配置工程配置 代码编写现象 DMA 在正式配置之前,我们先来一起简单了解一下DMA。DMA(Direct Memory Access,直接内…...
5G-A技术
最近的iOS 18.4 推送了 新功能,最引人注目的便是这个5G-A的这个功能,那什么是5G-A呢 ? 目前北京 四环内 还是有能显示出5G-A标志的。 5G-A 🌐 一句话概括: 5G-A 更快的速度 更低的延迟 更强的AI能力 更智能的网…...
Vue 组件 - 动态组件
Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue 组件 - 动态组件 目录 动态组件 选项卡页面示例 更简单写法 增加输入框 弥补措施 总结 动态组件 选项卡页面示例 功能:选项卡功能,设置导航点击哪个显示相应页面。 设置三个全局组件&#…...
ffmpeg滤镜使用
ffmpeg实现画中画效果 FFmpeg中,可以通过overlay将多个视频流、多个多媒体采集设备、多个视频文件合并到一个界面中,生成画中画的效果 FFmpeg 滤镜 overlay 基本参数 x和y x坐标和Y坐标 eof action 遇到 eof表示时的处理方式,默认为重复。…...
【MVC简介-产生原因、演变历史、核心思想、组成部分、使用场景】
MVC简介 产生原因: MVC(Model-View-Controller)模式诞生于20世纪70年代,由Trygve Reenskaug在施乐帕克研究中心(Xerox PARC)为Smalltalk语言设计,目的是解决图形用户界面(GUI&…...
基于大模型的房间隔缺损手术全流程预测与方案优化研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与目标 1.3 研究方法与创新点 二、房间隔缺损概述 2.1 房间隔缺损定义与分类 2.2 发病机制与病理生理 2.3 流行病学特征 三、大模型在房间隔缺损预测中的应用原理 3.1 大模型技术简介 3.2 数据收集与预处理 3.3 模型…...
什么是 CSSD?
文章目录 一、什么是 CSSD?CSSD 的职责 二、CSSD 是如何工作的?三、CSSD 为什么会重启节点?情况一:网络和存储都断联(失联)情况二:收到其他节点对自己的踢出通知(外部 fencing&#…...
uniapp APP端在线升级(简版)
设计思路: 1.版本比较:应用程序检查其当前版本与远程服务器上可用的最新版本 2. 更新状态指示:如果应用程序是不是最新的版本,则页面提示下载最新版本。 3.下载启动:通过plus.downloader.createDownload()启动新应用…...
2024年蓝桥杯Java B组省赛真题超详解析-分布式队列
问题:你需要回答在某个时刻,队列中有多少个元素具有可见性 方案:跟踪每个副节点已经同步到主节点队列的元素数量,并找出所有副节点中同步到的最少元素数量,这个数量即为所有副节点都已经同步的元素数量。 解析&#…...
Vue3入门
环境准备: node.js vscode or webstorm 哪个熟悉用哪个 这两个都是傻瓜式安装 浏览器直接搜索 下载即可 安装: 安装完node.js之后 按住快捷键 winR 打开命令提示符输入node 将显示版本信息 接着我们通过 vite 构建vue3工程 优点: 轻量快速的热重载(HMR…...
向量库(Vector Database)概述
向量库(Vector Database)概述 1. 核心概念 向量 高维空间中的数值数组,通常由模型(如BERT、ResNet)将非结构化数据(文本、图像等)转换为嵌入向量。 向量相似性 衡量方法:余弦相…...
Oracle迁移达梦遇中断?试试SQLark的断点续迁功能!
在企业级数据迁移项目中,如果迁移单表数据量超过亿行、占用空间超过100GB时,一旦遇到网络中断或迁移报错,往往需要整表重新迁移,导致效率低下,严重影响项目进度。针对这一痛点,SQLark 支持对 Oracle→DM 的…...
上海某海外视频平台Android高级工程师视频一面
问的问题比较细,有很多小细节在里面,平时真不一定会注意到,做一个备忘: 1.Object类里面有哪些方法? Object 类是 Java 中所有类的根类,它定义了一些基本方法,供所有类继承和重写1. 常用方法 1…...
基于yolov11的汽车损伤检测系统python源码+onnx模型+评估指标曲线+精美GUI界面
【算法介绍】 基于YOLOv11的汽车损伤检测系统是一种先进的计算机视觉技术,旨在快速准确地识别汽车的各种损伤类型。该系统利用YOLOv11模型的强大性能,实现了对车辆损伤的精确检测与分类。 该系统能够识别的损伤类型包括裂纹(crackÿ…...
华为IP(3)
DHCP Relay报文格式 DHCP Relay主要负责转发DHCP客户端与DHCP服务器之间的DHCP报文,所以DHCP Relay的报文格式只是把DHCP的报文部分字段做了相应的修改,报文格式没有发生变化 hops:表示当前DHCP报文经过DHCP中继的数目,该字段由…...
面试问题总结:qt工程师/c++工程师
C 语言相关问题答案 面试问题总结:qt工程师/c工程师 C 语言相关问题答案 目录基础语法与特性内存管理预处理与编译 C 相关问题答案面向对象编程模板与泛型编程STL 标准模板库 Qt 相关问题答案Qt 基础与信号槽机制Qt 界面设计与布局管理Qt 多线程与并发编程 目录 基础…...