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

linux入门---线程池的模拟实现

目录标题

  • 什么是线程池
  • 线程的封装
    • 准备工作
    • 构造函数和析构函数
    • start函数
    • join函数
    • threadname函数
    • 完整代码
  • 线程池的实现
    • 准备工作
    • 构造函数和析构函数
    • push函数
    • pop函数
    • run函数
    • 完整的代码
  • 测试代码

什么是线程池

在实现线程池之前我们先了解一下什么是线程池,所谓的池大家可以理解为一次性申请和创建很多的东西然后将其保存起来等未来需要的时候就不需要再创建和申请了,直接从保存的地方拿就行了,在生活中我们也经常用到池化技术比如说去菜场里面买菜并不是只卖一顿的菜而是买好几天要吃的菜,因为这么做的话可以大幅度的减少去菜场所带来的时间消耗,那么在计算机中也是同样的道理比如说使用函数new向操作系统申请10字节的空间时,操作系统并不是真的从内存中给你现场申请10字节的空间,而是当前的进程早就提前申请好了一大堆空间,把这部分空间保存下来,一旦你再申请的话将该空间的一部分给你就行并不会立马跑到内存上进行申请,这样做可以有效的提高程序的效率,那么这就是一个池化技术,那么这里的线程池也是同样的道理,我们可以先创建多个线程并将其保存下来,然后再创建一个容器用来接收任务,一旦有任务了就自动唤醒对应的线程将其进行处理即可无需再次创建,比如说下面的图片:
在这里插入图片描述
那么接下来我们将一步一步的实现线程池,首先来完成线程的封装。

线程的封装

准备工作

因为创建线程需要对应的执行函数和参数以及对应的pid_t变量,所以类中得有一个function对象和一个void*类型的指针和一个pid_td对象:

typedef std::function<void *(void *)> func_t;class Thread
{public:private:func_t func_;void *args_;pthread_t tid_;};

此外我们还希望该类能够有一个自己的名字,所以类中还有一个string的对象用来记录线程的名字,这里我们希望名字的形式为thread 1,thread 2…以此类推,所以我们在类中我们再添加一个静态的整形变量用来记录当前是创建的第几个线程,所以当前类的代码如下:

typedef std::function<void *(void *)> func_t;class Thread
{public:private:std::string name_;func_t func_;void *args_;pthread_t tid_;static int threadnum;};int Thread::threadnum = 1;

构造函数和析构函数

对于该类的设计我们是这样向的,创建类的时候并不创建对应的线程执行函数这个交给后面的start函数,构造函数要干的事情就是将该线程对应的名字创建好即可,那么这里创建一个char类型的数组,然后使用snprintf函数将对应的名字输入到这个数组里面,最后用这个数组的起始的地址构造string对象即可,那么这里的代码如下:

Thread()//构造函数初始化名字
{char namebuffer[num];//这里的num为全局变量后面会写snprintf(namebuffer, sizeof namebuffer, "thread-%d", threadnum++);name_ = namebuffer;
}

对于析构函数当前没有什么内容需要我们手动释放,所以析构函数什么都不用干:

~Thread()
{// do nothing
}

start函数

start函数干的事情就是使用pthread_start函数创建线程执行对应的函数,因为pthread_start函数需要调用对象以及函数的参数,所以strat函数需要两个参数,并将两个参数的值赋值给类中对应的对象:

void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{func_=func;args_=args;
}

然后我们就应该调用函数pthread_create来创建线程,这里本可以直接使用类内对象func_和args来完成线程的创建,但是我们这里另寻一路来带着大家回顾更多的知识,我们创建一个名为start_routine的类内函数,通过让pthread_create函数调用这个该来实现func_函数对象的调用

 void *start_routine(void *args) 
{}

我们知道pthread_create函数对要调用的函数是有要求的,要求它只有一个参数并且函数的返回值和参数的类型都得是void*,但是类内函数有一个特点就是自动的隐藏了一个this指针,也就是说上面的start_routine函数看上去只有一个参数实际上他有两个,所以这是不符合pthread_create函数的要求的,那么这里的解决办法就是将该函数改成静态函数,这样就没有了this指针但是这种做法又带来了另外一个问题就是func_是类内的非静态对象,静态函数是没有资格调用他的,所以为了解决这个问题我们在pthread_create函数传递参数的时候就传递一个this指针过去,这样静态函数就可以通过这个this指针访问非静态的成员或者函数:

static void *start_routine(void *args) 
{Thread *_this = static_cast<Thread *>(args);
}
void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{func_=func;args_=args;int n = pthread_create(&tid_, nullptr, start_routine, this);assert(n == 0);                                            (void)n;
}

可是这里还存在一个问题func_对象是私有的,通过类对象是访问不到的,所以这里我们还得创建一个接口函数用来访问func_对象,比如说下面的代码:

void *callback() 
{return func_(args_);
}//该函数调用类内的函数对象

然后就可以通过this指针调用这个callback函数调用func_对象:

void *callback() 
{return func_(args_);
}//该函数调用类内的函数对象
static void *start_routine(void *args) 
// 参数为指向该类对象的this指针所以可以访问到类内部的callback函数
{Thread *_this = static_cast<Thread *>(args);return _this->callback();
}
void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{func_=func;args_=args;int n = pthread_create(&tid_, nullptr, start_routine, this); assert(n == 0);                                            (void)n;
}

那么这就是start函数的实现过程。

join函数

该函数的实现就非常的简单,直接调用pthread_join函数即可,然后创建一个变量记录一下返回值并判断返回值是否为0:

void join()
{int n = pthread_join(tid_, nullptr);assert(n == 0);(void)n;
}

threadname函数

该函数的作用就是返回线程的名字,所以该函数的实现就直接返回类内的string对象即可,那么这里的代码如下:

string threadname()
{return name_;
}

完整代码

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include <pthread.h>
using namespace std;namespace ThreadNs
{typedef std::function<void *(void *)> func_t;const int num = 1024;class Thread{private:// 在类内创建线程,想让线程执行对应的方法,需要将方法设置成为staticstatic void *start_routine(void *args) // 参数为指向该类对象的this指针所以可以访问到类内部的callback函数{Thread *_this = static_cast<Thread *>(args);return _this->callback();}public:Thread()//构造函数初始化名字{char namebuffer[num];snprintf(namebuffer, sizeof namebuffer, "thread-%d", threadnum++);name_ = namebuffer;}void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务{func_=func;args_=args;int n = pthread_create(&tid_, nullptr, start_routine, this); assert(n == 0);                                            (void)n;}void join(){int n = pthread_join(tid_, nullptr);assert(n == 0);(void)n;}string threadname(){return name_;}~Thread(){// do nothing}void *callback() { return func_(args_);}//该函数调用类内的函数对象private:std::string name_;func_t func_;void *args_;pthread_t tid_;static int threadnum;};int Thread::threadnum = 1;
} // end namespace ThreadNs

线程池的实现

准备工作

既然是线程池,那么肯定得有一个变量用来记录池内线程的个数,还得有一个数组用来存储创建出来的Thread对象的指针,因为线程要获取任务,所以我们还得添加一个队列用来存储待处理的任务,因为在获取数据和存放数据的过程中可能会遇到多线程所带来的线程安全问题所以得添加锁变量,又因为存放数据和获取数据的过程是互斥的所以这个锁变量只能有一个,那么当前的代码就如下:

template<class T>
class ThreadPool
{
public:private:int _num;//表示线程池中线程的个数queue<T> _task_queue;//装载任务的队列vector<Thread*> _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};

构造函数和析构函数

构造函数只有一个参数用来表示当前的内存池有多少个线程并将该参数赋值给_num,然后在函数里面对条件变量和锁进行初始化,然后创建一个循环不断的创建Thread对象并将该对象的地址尾差到vector中

ThreadPool(int num=5)
:_num(num)
{pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i=0;i<num;i++){_threads.push_back(new Thread());}
}

析构函数干的事情就是将条件变量和锁变量进行销毁,然后通过for循环将vector对象中指针指向的空间进行回收即可,那么这里的代码如下:

~ThreadPool()
{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for(const auto& t:_threads){delete t;}
}

push函数

该函数的作用就是往任务队列中查入数据,所以该函数需要一个const T&类型的参数:

void push(const T&in)
{
}

因为插入数据的过程可能会出现线程安全问题,所以函数的第一步就是先对其进行枷锁,然后调用队列中的push函数将参数插入到队列里面,此时的队列中肯定有数据,所以在此之后就使用pthread_cond_signal函数将条件变量上等待的线程进行唤醒最后解锁即可:

void push(const T&in)
{pthread_mutex_lock(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);pthread_mutex_unlock(&_mutex);
}

pop函数

pop函数的作用是获取队列中的数据并删除,因为该函数是线程函数调用的,而多个线程在获取任务的时候要通过锁来保持互斥的关系,所以调用该函数的时候不用担心线程安全问题,所以在函数里面创建一个变量用来保存队列中的首元素数据并将其该数据删除,最后返回该变量即可:

T pop()
{T t=_task_queue.front();_task_queue.pop();return t;
}

run函数

run函数的功能就是将线程池中的每个线程都运行起来从队列中获取任务,那么这个时候就可以通过for循环的形式来调用Thread对象中的start函数来实现,因为start函数需要一个函数指针和一个参数,所以这里我们还得创建一个指定形式的函数,并且该函数还得是static类型

static void* handlerTask(void*args)
{}

在这个函数里面我们就可以实现从队列中获取任务并执行,所以该函数里面肯定得访问到类中的其他数据,所以我们传递给该函数的参数就是ThreadPool类型的this指针,但是这里我们还想实现一个功能就是在执行对应任务的时候想知道是哪个线程所执行的也就是知道对应线程的名字,所以按设想来说这里应该将对象的名字也传递给handlerTask函数,但是该函数只有一个参数传递了名字就传递不了this指针所以这里我们的做法就是再创建一个类,该类中有一个string对象用来存储名字和一个ThreadPool类型的指针用来访问类中的其他资源,那么这里的代码如下:

template<class T>
class ThreadPool;
template<class T>
class ThreadDate
{
public:ThreadPool<T>* threadpool;string name;ThreadDate(ThreadPool<T>*tp,const string& n):name(n),threadpool(tp){}
};
template<class T>
class ThreadPool
static void* handlerTask(void*args)
{}
public:
ThreadPool(int num=5)
:_num(num)
{pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i=0;i<num;i++){_threads.push_back(new Thread());}
}
void run()
{}
~ThreadPool()
{pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for(const auto& t:_threads){delete t;}
}
private:int _num;//表示线程池中线程的个数queue<T> _task_queue;//装载任务的队列vector<Thread*> _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};

那么在run函数里面要干的事情就是new一个ThreadDate对象,然后讲该对象的地址作为参数传递给start函数,并打印一句话用来表明当前的线程已经运转起来了:

void run()
{//这个函数就是将每个线程都启动for(const auto&t:_threads){ThreadDate<T>*td = new ThreadDate<T>(this,t->threadname());t->start(handlerTask,td);cout<< t->threadname() << " start... " <<endl;}
}

虽然handlerTask函数拥有了this指针,但是锁变量和条件变量等等都是私有的外界是无法访问到的,所以这里还得添加一些接口函数用来访问这些成员变量:

void lockQueue() { pthread_mutex_lock(&_mutex); }//对锁上锁
void unlockQueue() { pthread_mutex_unlock(&_mutex); }//对锁进行解锁
bool isQueueEmpty() { return _task_queue.empty(); }//判断当前的队列是否为空
void threadWait() { pthread_cond_wait(&_cond, &_mutex); }//把线程放到条件变量上进行等待

有了这些函数之后handlerTask函数就可以实现了,首先将参数的类型进行转换然后使用lockQueue函数进行枷锁,枷锁成功之后就创建一个循环使用isQueueEmpty函数来判断当前的队列中是否有任务,如果没有的话就使用threadWait函数将该线程挂起

static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。
{ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);while(true){td->threadpool->lockQueue();while(td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}
}

while循环结束就表明当前的队列存在数据,所以此时就可以通过 td中的成员变量访问到pop函数从而获取到队列中的任务,因为任务的执行并不会收到多线程之间的影响,所以得到任务之后就可以使用unlockQueue函数进行解锁,然后执行任务并打印执行之后的结果,最外层的while循环结束之后就可以使用delete销毁td指针指向的对象并返回nullptr:

static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。{ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);while(true){td->threadpool->lockQueue();while(td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}T t=td->threadpool->pop();td->threadpool->unlockQueue();string result=t();cout<<td->name<<" 处理了任务 "<<t.toTaskString()<<" 处理的结果为:"<<result<<endl;}delete td;return nullptr;}

完整的代码

#include<iostream>
#include<vector>
#include"Thread.hpp"
#include"LockGuard.hpp"
#include"Task.hpp"
#include<queue>
#include<string>
#include<unistd.h>
using namespace std;
using namespace ThreadNs;
template<class T>
class ThreadPool;
template<class T>
class ThreadDate
{
public:ThreadPool<T>* threadpool;string name;ThreadDate(ThreadPool<T>*tp,const string& n):name(n),threadpool(tp){}
};
template<class T>
class ThreadPool
{static void* handlerTask(void*args)//因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。//所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。{ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);while(true){td->threadpool->lockQueue();while(td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}T t=td->threadpool->pop();td->threadpool->unlockQueue();string result=t();cout<<td->name<<" 处理了任务 "<<t.toTaskString()<<" 处理的结果为:"<<result<<endl;}delete td;return nullptr;}
public:ThreadPool(int num=5):_num(num){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);//创建num个线程对象并将对象放到vectorfor(int i=0;i<num;i++){_threads.push_back(new Thread());}}void run(){//这个函数就是将每个线程都启动for(const auto&t:_threads){ThreadDate<T>*td = new ThreadDate<T>(this,t->threadname());t->start(handlerTask,td);cout<< t->threadname() << " start... " <<endl;}}T pop(){T t=_task_queue.front();_task_queue.pop();return t;}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for(const auto& t:_threads){delete t;}}void push(const T&in){pthread_mutex_lock(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);pthread_mutex_unlock(&_mutex);}void lockQueue() { pthread_mutex_lock(&_mutex); }void unlockQueue() { pthread_mutex_unlock(&_mutex); }bool isQueueEmpty() { return _task_queue.empty(); }void threadWait() { pthread_cond_wait(&_cond, &_mutex); }
private:int _num;//表示线程池中线程的个数queue<T> _task_queue;//装载任务的队列vector<Thread*> _threads;//装载线程pthread_mutex_t _mutex;pthread_cond_t _cond;
};

测试代码

我们可以用下面的代码来进行测试:

#include<iostream>
#include"ThreadPool.hpp"
using namespace std;
int main()
{ThreadPool<Task>* tp=new ThreadPool<Task>();tp->run();int x, y;char op;while(1){std::cout << "请输入数据1# ";std::cin >> x;std::cout << "请输入数据2# ";std::cin >> y;std::cout << "请输入你要进行的运算#";std::cin >> op;Task t(x, y, op, mymath);std::cout << "你刚刚录入了一个任务: " << t.toTaskString() << "确认提交吗?[y/n]# ";char confirm;std::cin >> confirm;if(confirm == 'y') tp->push(t);sleep(1);}
}

代码的运行结果如下:
在这里插入图片描述
可以看到符合我们的预期那么这就是实现的过程。

相关文章:

linux入门---线程池的模拟实现

目录标题 什么是线程池线程的封装准备工作构造函数和析构函数start函数join函数threadname函数完整代码 线程池的实现准备工作构造函数和析构函数push函数pop函数run函数完整的代码 测试代码 什么是线程池 在实现线程池之前我们先了解一下什么是线程池&#xff0c;所谓的池大家…...

jQuery HTML/CSS 参考文档

jQuery HTML/CSS 参考文档 文章目录 应用样式 示例属性方法示例 jQuery HTML/CSS 参考文档 应用样式 addClass( classes ) 方法可用于将定义好的样式表应用于所有匹配的元素上。可以通过空格分隔指定多个类。 示例 以下是一个简单示例&#xff0c;设置了para标签 <p&g…...

QT 布局管理综合实例

通过一个实例基本布局管理&#xff0c;演示QHBoxLayout类、QVBoxLayout类及QGridLayout类效果 本实例共用到四个布局管理器&#xff0c;分别是 LeftLayout、RightLayout、BottomLayout和MainLayout。 在源文件“dialog.cpp”具体代码如下&#xff1a; 运行效果&#xff1a; Se…...

使用 pubsub-js 进行消息发布订阅

npm 包地址 github 包地址 pubsub-js 是一个轻量级的 JavaScript 基于主题的消息订阅发布库 &#xff0c;压缩后小于1b。它具有使用简单、性能高效、支持多平台等优点&#xff0c;可以很好地满足各种需求。 功能特点&#xff1a; 无依赖同步解耦ES3 兼容。pubsub-js 能够在…...

TA Shader基础

渲染管线 概念&#xff1a;GPU绘制物体的时候&#xff0c;标准的&#xff0c;流水线一样的操作 游戏引擎如何绘制物体&#xff1a;CPU提供绘制数据&#xff08;顶点数据&#xff0c;纹理贴图等&#xff09;给GPU&#xff0c;配置渲染管线&#xff08;装载Shader代码到GPU&…...

VScode + opencv(cmake编译) + c++ + win配置教程

1、下载opencv 2、下载CMake 3、下载MinGW 放到一个文件夹中 并解压另外两个文件 4、cmake编译opencv 新建文件夹mingw-build 双击cmake-gui 程序会开始自动生成Makefiles等文件配置&#xff0c;需要耐心等待一段时间。 简单总结下&#xff1a;finish->configuring …...

Vue中的常用指令v-html / v-show / v-if / v-else / v-on / v-bind / v-for / v-model

前言 持续学习总结输出中&#xff0c;Vue中的常用指令v-html / v-show / v-if / v-else / v-on / v-bind / v-for / v-model 概念&#xff1a;指令&#xff08;Directives&#xff09;是Vue提供的带有 v- 前缀 的特殊标签属性。可以提高操作 DOM 的效率。 vue 中的指令按照不…...

ChatGPT 提问技巧

ChatGPT是由 OpenAI 训练的⼀款⼤型语⾔模型&#xff0c;能够和你进⾏任何领域的对话。 它能够⽣成类似于⼈类写作的⽂本。您只需要给出提示或提出问题&#xff0c;它就可以⽣成你想要的东⻄。 在此⻚⾯中&#xff0c;您将找到可与 ChatGPT ⼀起使⽤的各种提示。 只需按照下…...

2023-11-09 LeetCode每日一题(逃离火灾)

2023-11-09每日一题 一、题目编号 2258. 逃离火灾二、题目链接 点击跳转到题目位置 三、题目描述 给你一个下标从 0 开始大小为 m x n 的二维整数数组 grid &#xff0c;它表示一个网格图。每个格子为下面 3 个值之一&#xff1a; 0 表示草地。1 表示着火的格子。2 表示一…...

阿里云-maven私服idea访问私服与组件上传

1.进入aliyun制品仓库 2. 点击 生产库-release进入 根据以上步骤修改本地 m2/setting.xml文件 3.pom.xml文件 点击设置获取url 4. idea发布组件...

Ubuntu上的TFTP服务软件

2023年11月11日&#xff0c;周六下午 目录 tftpd-hpa atftpd 配置和启动 tftpd-hpa 这是一个TFTP服务器软件包&#xff0c;提供了一个简单的TFTP服务器。 你可以使用以下命令安装它&#xff1a; sudo apt-get install tftpd-hpaatftpd 这是另一个常用的TFTP服务器软件包…...

jedis、lettuce与redis交互分析

概念梳理&#xff1a; redis是缓存服务器&#xff0c;jedis、lettuce都是Java语言下的redis客户端&#xff0c;用于与redis服务器进行交互。springboot项目中一般使用的是spring data redis&#xff0c;spring data redis依赖与jedis或lettuce&#xff0c;可以进行配置&#x…...

C++算法:矩阵中的最长递增路径

涉及知识点 拓扑排序 题目 给定一个 m x n 整数矩阵 matrix &#xff0c;找出其中 最长递增路径 的长度。 对于每个单元格&#xff0c;你可以往上&#xff0c;下&#xff0c;左&#xff0c;右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外&#xff08;即不允…...

OpenWRT配置SFTP远程文件传输,让数据分享更安全

文章目录 前言 1. openssh-sftp-server 安装2. 安装cpolar工具3.配置SFTP远程访问4.固定远程连接地址 前言 本次教程我们将在OpenWRT上安装SFTP服务&#xff0c;并结合cpolar内网穿透&#xff0c;创建安全隧道映射22端口&#xff0c;实现在公网环境下远程OpenWRT SFTP&#xf…...

已解决:rm: 无法删除“/opt/module/zookeeper-3.4.10/zkData/zookeeper_server.pid“: 权限不够

解决&#xff1a; ZooKeeper JMX enabled by default Using config: /opt/module/zookeeper-3.4.10/bin/../conf/zoo.cfg Stopping zookeeper ... /opt/module/zookeeper-3.4.10/bin/zkServer.sh: 第 182 行:kill: (4149) - 不允许的操作 rm: 无法删除"/opt/module/zooke…...

Flink(四)【DataStream API - Source算子】

前言 今天开始学习 DataStream 的 API &#xff0c;这一块是 Flink 的核心部分&#xff0c;我们不去学习 DataSet 的 API 了&#xff0c;因为从 Flink 12 开始已经实现了流批一体&#xff0c; DataSet 已然是被抛弃了。忘记提了&#xff0c;从这里开始&#xff0c;我开始换用 F…...

GIS入门,xyz地图瓦片是什么,xyz数据格式详解,如何发布离线XYZ瓦片到nginx或者tomcat中

XYZ介绍 XYZ瓦片是一种在线地图数据格式,由goole公司开发。 与其他瓦片地图类似,XYZ瓦片将地图数据分解为一系列小的图像块,以提高地图显示效率和性能。 XYZ瓦片提供了一种开放的地图平台,使开发者可以轻松地将地图集成到自己的应用程序中。同时,它还提供了高分辨率图像和…...

[工业自动化-14]:西门子S7-15xxx编程 - 软件编程 - STEP7 TIA博途是全集成自动化软件TIA portal快速入门

目录 一、TIA博途是全集成自动化软件TIA portal快速入门 1.1 简介 1.2 软件常用界面 1.3 软件安装的电脑硬件要求 1.4 入口 1.5 主界面 二、PLC软件编程包含哪些内容 2.1 概述 2.2 电机运动控制 一、TIA博途是全集成自动化软件TIA portal快速入门 1.1 简介 Siemens …...

【教3妹学编程-算法题】Range 模块

3妹&#xff1a;哈哈哈哈哈哈哈哈 2哥 : 3妹看什么呢&#xff0c;笑的这么开森 3妹&#xff1a;2哥你快来看啊&#xff0c;成都欢乐谷的NPC模仿“唐僧”&#xff0c; 太搞笑了。 2哥 : 哦这个我也看到了&#xff0c;真的是唯妙唯肖&#xff0c;不能说像&#xff0c;只能说一模一…...

SpringBoot+MybatisPlus Restful示例

增删改查,分页 CREATE TABLE tbl_book ( id int NOT NULL AUTO_INCREMENT, type varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, name varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, desc_ription varchar(255) CHAR…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

【若依】框架项目部署笔记

参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作&#xff1a; 压缩包下载&#xff1a;http://download.redis.io/releases 1. 上传压缩包&#xff0c;并进入压缩包所在目录&#xff0c;解压到目标…...

计算机系统结构复习-名词解释2

1.定向&#xff1a;在某条指令产生计算结果之前&#xff0c;其他指令并不真正立即需要该计算结果&#xff0c;如果能够将该计算结果从其产生的地方直接送到其他指令中需要它的地方&#xff0c;那么就可以避免停顿。 2.多级存储层次&#xff1a;由若干个采用不同实现技术的存储…...

window 显示驱动开发-如何查询视频处理功能(三)

​D3DDDICAPS_GETPROCAMPRANGE请求类型 UMD 返回指向 DXVADDI_VALUERANGE 结构的指针&#xff0c;该结构包含特定视频流上特定 ProcAmp 控件属性允许的值范围。 Direct3D 运行时在D3DDDIARG_GETCAPS的 pInfo 成员指向的变量中为特定视频流的 ProcAmp 控件属性指定DXVADDI_QUER…...

SDU棋界精灵——硬件程序ESP32实现opus编码

一、 ​​音频处理框架​ 该项目基于Espressif的音频处理框架构建,核心组件包括 ESP-ADF 和 ESP-SR,以下是完整的音频处理框架实现细节: 1.核心组件 (1) 音频前端处理 (AFE - Audio Front-End) ​​main/components/audio_pipeline/afe_processor.c​​功能​​: 声学回声…...