【Linux】线程池与封装线程
目录
一、线程池:
1、池化技术:
2、线程池优点:
3、线程池应用场景:
4、线程池实现:
二、封装线程:
三、单例模式:
四、其他锁:
五、读者写者问题
一、线程池:
1、池化技术:
池化技术是以空间换时间的一种技术,上述的线程池实际上就是先提前创建一批线程用容器存储着,然后当有任务来临,线程直接从任务列表中获取并且执行任务,这样通过空间来换取时间,可以极大地提高效率
2、线程池优点:
高效,方便
- 线程池是已经创建好线程了,当有任务来临时,只需交给线程即可
- 在调度线程时保证合理性,防止线程的过度调度,保证内核的充分利用
- 避免了在处理短时间任务时创建与销毁线程的代价
但是线程池的数量也不是越多越好,更多的是要与实际开发相结合
3、线程池应用场景:
- 需要大量的线程来完成任务,并且每一个任务耗时短
- 对性能要求苛刻,要快速响应,比如打游戏时候放技能
- 当有可能突然出现大量请求的时候,但是不至于使服务器产生大量线程,也就是短时间产生大量线程使服务器内存达到最大,此时可以用线程池提升该效率问题
4、线程池实现:
#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <ctime>struct ThreadInfo
{pthread_t _tid;std::string ThreadName;
};static int defaultnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&_mutex);}void UnLock(){pthread_mutex_unlock(&_mutex);}void ThreadSleep(){pthread_cond_wait(&_cond,&_mutex);}bool IsEmpty(){return _task.empty();}std::string GetThreadName(pthread_t tid){for(auto &it : _threads){if(it._tid == tid){return it.ThreadName;}}return "NONE";}void Wakeup(){pthread_cond_signal(&_cond);}
public:ThreadPool(int num = defaultnum):_threads(num){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);}static void *myhander(void *args){ ThreadPool<T> * tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while(true){tp->Lock();while(tp->IsEmpty()){tp->ThreadSleep();}T ret = tp->Pop();tp->UnLock();ret();std::cout << name << "正在运行,结果: " << ret.Getresult() << std::endl;}}T Pop(){T t = _task.front();_task.pop();return t;}void Push(const T &t){Lock();_task.push(t);Wakeup();UnLock();}void Start(){int num = _threads.size();for(int i = 0;i < num;i++){_threads[i].ThreadName = "thread-" + std::to_string(i + 1);pthread_create(&(_threads[i]._tid),nullptr,myhander,this);}}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:std::vector<ThreadInfo> _threads;std::queue<T> _task;pthread_mutex_t _mutex;pthread_cond_t _cond;
};
对于上述线程池代码,有三个要注意的点:
1、myhander函数要设计成静态的,因为如果不设计成静态的,myhander就是成员方法,而成员方法有this指针,这样导致其形参不是void*,所以不能在创建线程的时候直接传,要设置成静态的
2、但是设置成静态的函数的话,发现编译不过,因为静态的函数可以访问其静态的函数和变量,无法访问成员函数和成员变量,所以在创建线程的时候要传递当前对象,也就是this指针,这样就能够通过对象访问其成员函数了
3、这里要引入互斥锁,这是因为线程池中的任务队列会被多个执行流访问,要加锁对其进行保护,我们知道线程执行任务的时候要去任务队列中拿任务,此时就需要判断任务队列是否为空,所以加锁要在判断之前
判断就是如果队列为空,就让当前线程去条件变量中休眠,还有为了防止伪唤醒,所以这里的if要改为while
4、当线程拿到任务的时候,这个任务就是当前线程私有的了,所以处理任务就不要放在临界区了,因为要保证在处理任务的线程是并行的,如果一个线程要等待另一个线程处理完任务就不是并行的了,效率会大大降低
任务设计
我们的任务在之前章节就已经完成了,直接拿过来用即可
#pragma once
#include <iostream>
#include <string>std::string opers = "+-*/%";enum
{Divzero = 1,Modzero,Unknow
};class Task
{
public:Task(){}Task(int data1, int data2, char oper): _data1(data1), _data2(data2), _oper(oper), _exitcode(0),_result(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 = Unknow;break;}}void operator ()(){run();}std::string Getresult(){std::string ret = std::to_string(_data1);ret += _oper;ret += std::to_string(_data2);ret += "=";ret += std::to_string(_result);ret += "[exitcode=";ret += std::to_string(_exitcode);ret += "]";return ret;}std::string GetTask(){std::string ret = std::to_string(_data1);ret += _oper;ret += std::to_string(_data2);ret += "=?";return ret;}~Task(){}private:int _data1;int _data2;char _oper;int _exitcode;int _result;
};
主函数逻辑
主线程就不断地向线程池中push任务即可,然后线程池就会从任务队列中拿任务并处理,怎么处理不用关心,所以这里对我们来说相当于一个黑盒
#include "ThreadPool.hpp"
#include "Task.hpp"int main()
{srand(time(nullptr)^getpid());ThreadPool<Task> *tp = new ThreadPool<Task>();tp->Start();while(true){int data1 = rand()%10+1;usleep(10);int data2 = rand()%5;char op = opers[rand()%opers.size()];Task t(data1,data2,op);usleep(10);tp->Push(t);std::cout<<"主线程启动:" << t.GetTask() <<std::endl;sleep(1);}return 0;
}
实验结果:
如下,并且我们发现这里的线程是有顺序性的,这是因为主线程每秒只会push一个任务,然后线程池中每次都只有一个线程获取到任务,其他线程就会在队列中等待,当这个线程处理完任务后,就会到队列的最后等待,此时若主线程有push任务,就会唤醒队列第一个线程,完成任务后又会到队列尾部,这样我们就能够看到顺序性
二、封装线程:
接下来我们尝试封装线程,就像C++中的线程库那样使用而不是使用原生接口
对于一个线程,我们可以给他设置很多属性,其中_data是传进来的参数,_cb是一个回调函数
private:pthread_t _tid;std::string _name;bool _isrunning;uint64_t _start_time;T _data;callback_t _cb;
将这些在构造函数进行初始化:
Thread(callback_t cb,T data):_tid(0),_name(""),_isrunning(false),_start_time(0),_cb(cb),_data(data){}
然后设计对外开放的接口,能够像C++库中的线程使用起来
void Run(){_name = "thread-" + std::to_string(num++);_isrunning = true;_start_time = time(nullptr);pthread_create(&_tid,nullptr,Running,this);}void Entry(){_cb(_data);}void Join(){_isrunning = false;pthread_join(_tid,nullptr);}std::string GetName(){return _name;}uint64_t Start_Time(){return _start_time;}bool IsRunning(){return _isrunning;}
其中,Running方法要设计成静态的,这是因为和线程池那里一样,如果不是静态方法就是成员函数,这样的话就会多一个形参,所以要设置成静态的,但是静态方法不能直接访问成员变量,所以在创建线程的时候要将this指针也就是当前对象传过去,这样就能够通过对象访问成员函数或成员变量了
static void* Running(void *args){Thread* thread = static_cast<Thread*>(args);thread->Entry();return nullptr;}
这里还要设置回调函数
typedef void (*callback_t)(T);
其中callback_t是 函数指针类型,他的参数是T,在定义一个Thread类的时候,需要传递一个函数给构造函数,这个cb就是一个函数指针,然后赋值给_cb,后面_cb()调用的就是传递进来的函数
在Entry中的cb(_data)调用的是这个uPrint
主函数逻辑
初始化好要传的参数,准备好回调函数所用的函数,就可以像C++中的线程库进行使用了
class udata
{
public:std::string _s;int _data;
};void uPrint(udata x)
{while(true){std::cout << "我是一个封装后的线程,我得到了参数 : " << x._s << " 和 " << x._data <<std::endl;sleep(1);}
}int main()
{udata da;da._s = "666";da._data = 111;Thread<udata> t(uPrint,da);t.Run();std::cout << "该线程的名字 : " << t.GetName() <<std::endl;std::cout << "该线程是否运行 : " << t.IsRunning() <<std::endl;std::cout << "该线程创建时间 : " << t.Start_Time() <<std::endl;t.Join();return 0;
}
三、单例模式:
具体可以看看下面这篇文章
【C++11】特殊类的设计 && 单例模式 && 类型转换-CSDN博客https://blog.csdn.net/2303_80828380/article/details/147028880?spm=1001.2014.3001.5501
在这里我们是将单例模式在代码中实现出来,也就是将线程池设计成单例模式
1、将构造函数私有化,保证不能够在外部随便创建变量
private:ThreadPool(int num = defaultnum): _threads(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}
2、将拷贝构造和赋值重载删掉,保证不能够通过拷贝来创建新对象
ThreadPool(const ThreadPool<T>& copy) = delete;const ThreadPool<T>& operator=(const ThreadPool<T>& copy) = delete;
3、设计静态的指针指向堆空间,还要有锁,_tp要设计成静态的原因实现单例模式,保证全局唯一实例,_lock为静态的提供全局同步锁,确保多线程下安全创建单例
静态成员变量在类里面声明,在类外面定义,是语法规定的
类内定义,类外初始化,在初始化的时候不要带上static,并且要指定类域,不然的话编译器编译的时候就找不到对应的变量了
template <class T>
class ThreadPool
{
private:static ThreadPool<T>* _tp;//不可以像下面初始化//static ThreadPool<T>* _tp = nullptr;static pthread_mutex_t _lock;
};template <class T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
4、这里采用懒汉模式创建单例,所以在GetInstance接口中new对象
这里这个函数为静态的,为了访问静态变量_tp,如果不是静态函数的话就访问不了_tp了
static ThreadPool<T>* GetInstance(){if(_tp == nullptr) {pthread_mutex_lock(&_lock);if(_tp == nullptr){_tp = new ThreadPool<T>;}pthread_mutex_unlock(&_lock);}return _tp;}
还要进行两次判空:
我们发现,只有第一个线程进入的时候_tp才会为空,这样的话后面如果多次进行加锁判断释放锁会降低效率,所以这里得二次判断,保证后面不为空的时候直接返回,并且即使第一次有多个线程进入了第一个if,但是,依然是只会有一个线程成功申请锁并进入if,new空间,其余线程即使后来申请到锁了,但是if判断失效,所以就会释放锁,然后后来的线程就会在第一个if那里都进不去,进而增加效率
完整代码:
#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <ctime>struct ThreadInfo
{pthread_t _tid;std::string ThreadName;
};static int defaultnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&_mutex);}void UnLock(){pthread_mutex_unlock(&_mutex);}void ThreadSleep(){pthread_cond_wait(&_cond, &_mutex);}bool IsEmpty(){return _task.empty();}std::string GetThreadName(pthread_t tid){for (auto &it : _threads){if (it._tid == tid){return it.ThreadName;}}return "NONE";}void Wakeup(){pthread_cond_signal(&_cond);}public:static void *myhander(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsEmpty()){tp->ThreadSleep();}T ret = tp->Pop();tp->UnLock();ret();std::cout << name << "正在运行,结果: " << ret.Getresult() << std::endl;}}T Pop(){T t = _task.front();_task.pop();return t;}void Push(const T &t){Lock();_task.push(t);Wakeup();UnLock();}void Start(){int num = _threads.size();for (int i = 0; i < num; i++){_threads[i].ThreadName = "thread-" + std::to_string(i + 1);pthread_create(&(_threads[i]._tid), nullptr, myhander, this);}}//懒汉模式static ThreadPool<T>* GetInstance(){if(_tp == nullptr) //我们发现,只有第一个线程进入的时候_tp才会为空,这样的话后面如果多次进行加锁判断释放锁会降低效率{ //所以这里得二次判断,保证后面不为空的时候直接返回,并且即使第一次有多个线程进入了235行,但是//依然是只会有一个线程成功申请锁并进入if,new空间,其余线程即使后来申请到锁了,但是if判断失效,所以就//会释放锁,然后后来的线程就会在第一个if那里都进不去,进而增加效率pthread_mutex_lock(&_lock);if(_tp == nullptr){_tp = new ThreadPool<T>;}pthread_mutex_unlock(&_lock);}return _tp;}private:ThreadPool(int num = defaultnum): _threads(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}ThreadPool(const ThreadPool<T>& copy) = delete;const ThreadPool<T>& operator=(const ThreadPool<T>& copy) = delete;
private:std::vector<ThreadInfo> _threads;std::queue<T> _task;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T>* _tp;//static ThreadPool<T>* _tp = nullptr;static pthread_mutex_t _lock;
};template <class T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
四、其他锁:
- 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
- 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
- CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
- 自旋锁:申请锁失败的时候不将自己挂起,而是不断尝试申请锁
自旋就是不把自己挂起,而是由线程不断地去周而复始的去申请锁,如果申请锁成功则进入,失败则返回重新检测锁的状态
自旋锁相关接口:
自旋锁的类型是pthread_spinlock_t
加解自旋锁:
其中:
int pthread_spin_lock(pthread_spinlock_t *lock)失败就不断重试(阻塞式)
int pthread_spin_trylock(pthread_spinlock_t *lock);失败就继续向后运行(非阻塞式)
其实是类似于之前学习的互斥锁,使用和其大差不差
什么时候使用自旋锁:
1、锁持有时间极短
当临界区代码执行时间非常短(如几个指令周期)时,自旋锁的“忙等待”不会显著浪费CPU资源
2、多核/多CPU环境
在多核系统中,等待线程可能在另一个核心上运行,避免因睡眠-唤醒带来的上下文切换开销
3、不可睡眠的上下文
在中断上下文、内核抢占禁用区域等不能睡眠的代码路径中,必须使用自旋锁
4、对实时性要求高
自旋锁的响应延迟低(无上下文切换),适合实时系统或低延迟任务
特性 | 自旋锁 | 互斥锁 |
---|---|---|
等待方式 | 忙等待(循环检测) | 睡眠等待(进入阻塞状态) |
CPU消耗 | 高(持续占用CPU) | 低(释放CPU给其他线程) |
适用场景 | 短临界区、多核、不可睡眠 | 长临界区、通用场景 |
性能优势 | 低延迟(无上下文切换) | 高吞吐量(减少CPU浪费) |
五、读者写者问题
这个也是遵循"321"原则的
3种关系:
读者和读者,写者和写者,读者和写者
2个角色:
读者和写者
1个交易场所:
阻塞队列或者缓冲区
以上基本是和生产消费者模型是差不多的,不同的是读者和读者之间是没有互斥关系的,最大的原因在于读者是只会读缓冲区中的数据,是不会将数据拿走或者修改的,所以不需要维护读者和读者之间的互斥关系
相关接口:
注意:读者和写者的锁不是同一个锁
初始化,销毁自旋锁:
读者进行加锁,解锁
写者进行加锁,解锁
解锁,这个读者写者的锁都能解
读者写者的伪代码:
理解:
读者在读数据的时候,写者是不能够写的,但是其他读者可以读
写者在写数据的时候,读者是不能够读数据的
当所有读者读完数据的时候,再让写者写数据
当写者写完数据的时候,再让读者读数据
接下来实现一个读者优先的伪代码:
void Reader()
{// 首先就是读者的加锁和解锁操作:// 首先对读者进行加锁pthread_mutex_lock(&rlock);reader_count++; //在这里将读者的数量+1// 接着判断读者是否为1,如果为1就证明这是第一个读者,此时申请写者的锁,就会有两种情况// 1、写者没有进行写入,所以写者也就没有申请到锁,所以读者申请成功锁,让以后写者无法进行写入// 2、写者正在进行写入,所以写者也就持有锁,那么第一个读者也就在这里申请失败锁,进入阻塞队列中等待if (reader_count == 1) {pthread_mutex_lock(&wlock);}// 这里是释放的读者自己的锁,如果读者正在读,一定会持有写者的锁的pthread_mutex_unlock(&rlock);// 在这里进行读取,因为不需要维护读者之间的互斥关系也就不需要锁,直接进行读取即可//当读取完后,下面就是减少读者的代码pthread_mutex_lock(&rlock);reader_count--; // 将读者的数量-1//这里进行判断,如果读者的数量为0的时候释放写者的锁,此时写者就能够进行写入了if (reader_count == 0) {pthread_mutex_lock(&wlock);}pthread_mutex_unlock(&rlock);
}
void Writer()
{// 然后是写者的加锁,写入数据,解锁pthread_mutex_lock(&wlock);//写入数据pthread_mutex_unlock(&wlock);
}
相关文章:
【Linux】线程池与封装线程
目录 一、线程池: 1、池化技术: 2、线程池优点: 3、线程池应用场景: 4、线程池实现: 二、封装线程: 三、单例模式: 四、其他锁: 五、读者写者问题 一、线程池: …...
protobuf的应用
1.版本和引用 syntax "proto3"; // proto2 package tutorial; // package类似C命名空间 // 可以引用本地的,也可以引用include里面的 import "google/protobuf/timestamp.proto"; // 已经写好的proto文件是可以引用 我们版本选择pr…...
linux shell编程之条件语句(二)
目录 一. 条件测试操作 1. 文件测试 2. 整数值比较 3. 字符串比较 4. 逻辑测试 二. if 条件语句 1. if 语句的结构 (1) 单分支 if 语句 (2) 双分支 if 语句 (3) 多分支 if 语句 2. if 语句应用示例 (1) 单分支 if 语句应用 (2) 双分支 if 语句应用 (3) 多分支 …...
图论整理复习
回溯: 模板: void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯ÿ…...
企业指标设计方法指南
该文档聚焦企业指标设计方法,适用于企业中负责战略规划、业务运营、数据分析、指标管理等相关工作的人员,如企业高管、部门经理、数据分析师等。 主要内容围绕指标设计展开:首先指出指标设计面临的困境,包括权责不清、口径不统一、缺乏标准规范、报表体系混乱、指标…...
AIP-217 不可达资源
编号217原文链接AIP-217: Unreachable resources状态批准创建日期2019-08-26更新日期2019-08-26 有时,用户可能会请求一系列资源,而其中某些资源暂时不可用。最典型的场景是跨集合读。例如用户可能请求返回多个上级位置的资源,但其中某个位置…...
SAP系统控制检验批
问题:同一批物料多检验批问题 现象:同一物料多采购订单同一天到货时,对其采购订单分别收货,导致系统产生多个检验批,需分别请检单、检验报告等,使质量部工作复杂化。 原因:物料主数据质量试图设…...
JavaScript 代码混淆与反混淆技术详解
一、代码混淆:让别人看不懂你的代码 混淆技术就是一种“代码伪装术”,目的是让别人很难看懂你的代码逻辑,从而保护你的核心算法或敏感信息。 1. 变量名压缩 原理:把变量名改成乱码,比如把calculatePrice改成a&#…...
Android studio | From Zero To One ——手机弹幕
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 滚动显示 代码activity_main.xmlactivity_fullscreen.xmlAndroidManife…...
面向对象的需求分析与UML构造块详解
目录 前言1 面向对象的需求分析概述2 UML构造块概述3 UML事物详解3.1 结构事物(Structural Things)3.2 行为事物(Behavioral Things)3.3 分组事物(Grouping Things)3.4 解释事物(Annotational T…...
LeetCode 2843.统计对称整数的数目:字符串数字转换
【LetMeFly】2843.统计对称整数的数目:字符串数字转换 力扣题目链接:https://leetcode.cn/problems/count-symmetric-integers/ 给你两个正整数 low 和 high 。 对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字…...
RocketMQ深度百科全书式解析
一、核心架构与设计哲学 1. 设计目标 海量消息堆积:单机支持百万级消息堆积,适合大数据场景(如日志采集)。严格顺序性:通过队列分区(Queue)和消费锁机制保证局部顺序。事务…...
A2A与MCP Server:AI智能体协作与工具交互的核心协议对比
A2A与MCP Server:AI智能体协作与工具交互的核心协议对比 摘要 在AI智能体技术爆发式增长的今天,谷歌的A2A协议与Anthropic的MCP协议正在重塑AI系统架构。本文通过协议栈分层模型、企业级架构设计案例及开发者实践指南三大维度,揭示二者在AI生…...
如何将网页保存为pdf
要将网页保存为PDF,可以按照以下几种方法操作: 1. 使用浏览器的打印功能 大多数现代浏览器(如Chrome、Firefox、Edge等)都支持将网页保存为PDF文件。步骤如下: 在 Google Chrome 中: 打开你想保存为PDF…...
位运算与实战场景分析-Java代码版
一、为什么每个程序员都要掌握位运算? 在电商秒杀系统中,位运算可以快速判断库存状态;在权限管理系统里,位运算能用极小的空间存储复杂权限配置;在算法竞赛中,位运算更是高频出现的性能优化利器。这项看似…...
【“星睿O6”AI PC开发套件评测】+ Debian 系统安装及 sysbench 跑分对比
很荣幸这次可以得到机会评测 “星睿O6”AI PC开发套件。第一篇文章,我将分为两个部分: 官方 Debian 系统安装到 NVMEsysbench 跑分以及对比 RK3568 和 I712700KF 正文开始之前,忍不住还是想放几张开箱照片,板子实在是太精致了。…...
java——继承
继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。继承是指在原有类的基础上,进行功能扩展,创建新的类型。 概念与作用 代码复用:继承能够避免重…...
STM32嵌入式开发从入门到实战:全面指南与项目实践
STM32嵌入式开发从入门到实战:全面指南与项目实践 一、STM32开发基础概述 1.STM32微控制器核心特性 STM32微控制器基于ARM Cortex - M内核,具备显著的架构优势。其32位处理能力,能够高效处理复杂的计算任务,相较于传…...
企业数据孤岛如何破
企业数据孤岛如何破 背景信息传统方式Flink CDC如何用技术之力 背景信息 在数字化转型的浪潮中,企业数据的价值正从“事后分析”向“实时驱动”快速迁移。企业需要快速、高效地将分散在不同系统中的数据整合起来,以支持实时分析和业务决策。诚然&#x…...
源码编译安装Nginx
源码编译安装Nginx 源码编译安装Nginx创建nginx服务用户安装编译环境依赖包下载Nginx源码构建编译选项,创建makefile文件编译安装nginx为Nginx创建服务单元设置Nginx开机自启服务 yum安装Nginxyum安装openresty 源码编译安装Nginx 如果需要最新版本及定制化模块可以通过源码安…...
查看容器内的eth0网卡对应宿主机上的哪块网卡
查看容器内的eth0网卡对应宿主机上的哪块网卡 问题描述解决办法1. 进入容器,查看网卡的iflink(接口链路索引)值方法1:方法2: 2. 从宿主机过滤查询到的iflink(接口链路索引)值3. 确定veth接口连接的网桥方法2: brctl查看连接到网桥的接口--推荐 4. 查看网桥连接的物理网卡 问题描…...
虚拟偶像“C位出道”:数字浪潮下的崛起与财富密码(3/10)
摘要:虚拟偶像作为数字时代的新宠,凭借数字技术与文化创意的深度融合,在全球范围内迅速崛起。从早期的简单2D形象到如今高度逼真、智能交互的3D虚拟偶像,其发展得益于计算机图形学、动作捕捉、AI等技术的进步。虚拟偶像不仅在娱乐…...
swift菜鸟教程13(函数)
一个朴实无华的目录 今日学习内容:1.Swift 函数1.1函数定义:使用关键字 func。1.2函数参数:以逗号分隔。1.3不带参数函数1.4元组作为函数返回值1.5没有返回值函数1.6函数参数名称1.6.1局部参数名1.6.2外部参数名 1.7可变参数1.8常量ÿ…...
MacOS红队常用攻击命令
MacOS红队常用攻击命令 1.自动化武器2.系统信息3.服务 & 内核信息4.快捷命令5.网络相关6.brew相关 / 软件包相关7.高权限命令8.创建一个管理员权限的后门用户 1.自动化武器 1、linPEAS LinPEAS 是一个脚本,用于在 Linux/Unix/MacOS 主机上搜索提权路径 2、me…...
无人机的振动与噪声控制技术!
一、振动控制技术要点 1. 振动源分析 气动振动:旋翼桨叶涡脱落(如叶尖涡干涉)、动态失速(Dynamic Stall)引发的周期性气动激振力(频率与转速相关)。 机械振动:电机偏心、传动轴不…...
如何使用 Spring Boot 实现分页和排序?
全文目录: 开篇语1. 创建 Spring Boot 项目2. 配置数据库连接3. 创建实体类4. 创建 Repository 接口5. 创建分页和排序服务6. 创建控制器7. 测试分页和排序请求示例:返回结果: 8. 总结 文末 开篇语 哈喽,各位小伙伴们,…...
浅谈编译型语言的运用
如大家所熟悉的,程序在执行之前需要一个专门的编译过程,把程序编译成机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了,程序执行效率高,依赖编译器,如 C/C、Golang 等,…...
知识了解02——了解pnpm+vite+turbo+monorepo的完整构建步骤(react子项目)
(1)初始化monorepo 1)创建项目目录并进入当前目录 2)初始化 pnpm 工作区,生成一个package.json文件 3)在项目根目录下创建 pnpm-workspace.yaml 文件,并定义工作区目录 (2)安装 Turborepo 1)安…...
MySQL 半同步复制,给数据找靠谱 “分身”
目录 一背景 二、MySQL 复制基础概念 为何需要 MySQL 复制 传统异步复制 半同步复制的诞生 三、MySQL 半同步复制原理详解 主要组件及作用 工作流程 半同步复制流程图 四、MySQL 半同步复制配置与代码示例 环境准备 主服务器配置 从服务器配置 示例说明 五、MyS…...
uniapp离线打包提示未添加videoplayer模块
uniapp中使用到video标签,但是离线打包放到安卓工程中,运行到真机中时提示如下: 解决方案: 1、把media-release.aar、weex_videoplayer-release.aar放到工程的libs目录下; 文档:https://nativesupport.dcloud.net.cn/…...
机器人零位标定修正流程介绍
如果想看运动学标定可以看看 机器人运动学参数标定, 一次性把运动学参数和零位标定等一起标定求解. 1. 零位标定 零位标定是机器人运动学标定中的一个重要步骤,其目的是校正机器人关节的初始位置误差。以下是需要进行零位标定的主要原因: 制造误差 在机…...
应用层通信报文设计
/* --------------------------------------------------------------- | 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte | --------------------------------------------------------------- | 状态 1byte | 保留字段 4byte | 数据长…...
一周学会Pandas2 Python数据处理与分析-Pandas2读取Excel
锋哥原创的Pandas2 Python数据处理与分析 视频教程: 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili Excel格式文件是办公使用和处理最多的文件格式之一,相比CSV文件,Excel是有样式的。Pandas2提…...
技术分享|iTOP-RK3588开发板Ubuntu20系统旋转屏幕方案
iTOP-3588开发板采用瑞芯微RK3588处理器,是全新一代AloT高端应用芯片,采用8nmLP制程,搭载八核64位CPU,四核Cortex-A76和四核Cortex-A55架构,主频高达2.4GHz。是一款可用于互联网设备和其它数字多媒体的高性能产品。 在…...
ubuntu 20.04 安装源码编译 ros humble过程
公司要兼容ros1还需要ros2 这个时候不得不使用ubuntu20.04 安装 humble 但实际上在20.04上安装humble是需要在源码编译的。 根据这个帖子 https://blog.csdn.net/m0_62353836/article/details/129730981 重写一份,以应对无法下载的问题 系统配置 #检查是否为UTF-8编码,是则跳…...
Ubuntu18.04.06安装window虚拟机,安装VirtualBox
VirtualBox官网没有支持Ubuntu18的版本,最低是ubuntu20; 但是现在用的系统是UBuntu18.04.06,又不能升级,查阅了很多办法,最终终于安装VirtualBox可用版本; 1,在Ubuntu18自带的软件应用市场,搜VirtualBox;…...
Matlab 四分之一车体被动悬架、pid、模糊控制和模糊pid控制
1、内容简介 Matlab 198-四分之一车体被动悬架、pid、模糊控制和模糊pid控制 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...
Linux-----驱动
一、内核驱动与启动流程 1. Linux内核驱动 Nor Flash: 可线性访问,有专门的数据及地址总线(与内存访问方式相同)。 Nand Flash: 不可线性访问,访问需要控制逻辑(软件)。 2. Linux启动流程 ARM架构: IRAM…...
用HTML和CSS绘制佩奇:我不是佩奇
在这篇博客中,我将解析一个完全使用HTML和CSS绘制的佩奇(Pig)形象。这个项目展示了CSS的强大能力,仅用样式就能创造出复杂的图形,而不需要任何图片或JavaScript。 项目概述 这个名为"我不是佩奇"的项目是一个纯CSS绘制的卡通猪形象…...
Qwen2.5-7B-Instruct FastApi 部署调用教程
1 环境准备 基础环境最低要求说明: 环境名称版本信息1Ubuntu22.04.4 LTSCudaV12.1.105Python3.12.4NVIDIA CorporationRTX 3090 首先 pip 换源加速下载并安装依赖包 # 升级pip python -m pip install --upgrade pip # 更换 pypi 源加速库的安装 pip config set g…...
潇洒浪: Dify 上传自定义文件去除内容校验 File validation failed for file: re.json
Dify上传文件 添加其他文件类型如 my.myselfsuffix 上传成功 执行报错 File validation failed for file: re.json 解决办法 Notepad 搜索dify源码 注释掉,重启容器 或者直接在容器中修改重启...
【力扣hot100题】(088)最长有效括号
这题目真是越做越难了。 但其实只是思路很难想到,一旦会了方法就很好做。 但问题就在方法太难想了…… 思路还是只要遍历一遍数组,维护动态规划数组记录截止至目前位置选取该元素的情况下有效括号的最大值。 光是知道这个还不够,看了答案…...
XML、JSON 和 Protocol Buffers (protobuf) 对比
目录 1. XML (eXtensible Markup Language) 1)xml的特点: 2)xml的适用场景: 2. JSON (JavaScript Object Notation) 1)JSOM的特点: 2)JSON的适用场景: 3. Protocol Buffers (…...
C++ 入门四:类与对象 —— 面向对象编程的核心基石
一、类的定义 1. 类的基本形式 class 类名 { public: // 公有成员(类内外均可访问)数据类型 数据成员; // 公有数据成员数据类型 成员函数(参数列表); // 公有成员函数声明 protected: // 保护成员(类内和派生类可访问&…...
DeepSeek:穿透行业知识壁垒的搜索引擎攻防战
DeepSeek:穿透行业知识壁垒的搜索引擎攻防战 文 / 产业智能观察组(人机协同创作) 一、搜索引擎的"认知折叠"危机 2024年Q1数据显示,百度搜索结果前10页中,61.7%的内容存在"伪专业化"现象——看似…...
SQL 查询中涉及的表及其作用说明
SQL 查询中涉及的表及其作用说明: 涉及的数据库表 表名别名/用途关联关系dbo.s_orderSO(主表)存储订单主信息(订单号、日期、客户等)dbo.s_orderdetailSoD(订单明细)通过 billid SO.billid 关…...
数组 array
1、数组定义 是一种用于存储多个相同类型数据的存储模型。 2、数组格式 (1)数据类型[ ] 变量名(比较常见这种格式) 例如: int [ ] arr0,定义了一个int类型的数组,数组名是arr0; &am…...
Git 查看提交历史
Git作为最流行的版本控制工具,其提交历史管理是开发者日常工作的核心部分。无论是回溯代码变更、定位问题根源,还是进行版本回退,掌握Git提交历史的操作技巧都至关重要。本文将全面解析Git提交历史相关命令,助你成为版本管理高手&…...
电脑提示“找不到mfc140u.dll“的完整解决方案:从原因分析到彻底修复
当你启动某个软件或游戏时,突然遭遇"无法启动程序,因为计算机中丢失mfc140u.dll"的错误提示,这确实令人沮丧。mfc140u.dll是Microsoft Foundation Classes(MFC)库的重要组成部分,属于Visual C Re…...
windows安卓子系统wsa隐藏应用列表的安装激活使用
Windows 11 安卓子系统应用部署全攻略 windows安卓子系统wsa隐藏应用列表的安装激活使用|过检测核心前端 在 Windows 11 系统中,安卓子系统为用户带来了在电脑上运行安卓应用的便利。经过一系列的操作,我们已经完成了 Windows 11 安卓子系统的底层和前端…...