linux线程 | 同步与互斥 | 线程池以及知识点补充

        前言:本节内容是linux的线程的相关知识。本篇首先会实现一个简易的线程池, 然后再将线程池利用单例的懒汉模式改编一下。 然后再谈一些小的知识点,比如自旋锁, 读者写者问题等等。 那么, 现在开始我们的学习吧。

        ps:本节内容设计到了单例模式, 建议了解单例设计模式以及多线程, 生产消费者模型的友友们进行观看哦。

目录

 线程池

 什么是线程池

线程池的应用场景

代码实现 

准备文件

makefile

Task.h

ThreadPool.h

main.cpp

运行结果

单例模式

常见的锁

自旋锁

自旋锁接口

读者写者问题 

概念

 接口

理解


 线程池

 什么是线程池

        线程池就是一种线程的使用模式。 线程的过多会带来调度的开销, 进而影响缓存局部性和整体的性能。 而线程池维护者多个线程, 等待着监督管理者分配可并发执行的任务。 这避免了在处理短时间任务时创建与销毁线程的代价。 线程池不仅能够保证内核的充分利用, 还能防止过度调用。 

线程池的应用场景

  •         需要大量的线程来完成任务,且完成任务的时间比较短。 单个任务小, 任务数量大更加适合!对于任务很大(时间很长)的场景, 线程池的有点就不明显了。 因为如果一个任务很大的话, 那这个任务比创建线程的时间长的多, 就不明显。 
  •         对于性能要求比较苛刻, 比如要求服务器迅速相应客户请求。 
  •         接受突发性的大量请求, 但不至于使服务器因此产生大量线程的引用。 突发性大量客户请求, 在没有线程池情况下, 将产生大量线程, 虽然理论上大部分操作系统县城数据最大值不是问题, 短时间内产生大量线程可能使内存到达极限, 出现错误。 

代码实现 

准备文件

makefile

main.exe:main.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -rf main.exe

Task.h

#include <iostream>
using namespace std;
#include <vector>
#include <string>//加减乘除
string opers = "+-*/%";//Task.h文件里面包含了任务类, 这个是我们线程池要执行的任务
class Task
{
public://构造函数, 第一个参数data1, 第二个参数data2, 第三个参数是加减乘除的符号。 这个任务就是进行四则运算Task(int x, int y, char op): data1_(x), data2_(y), op_(op){}~Task() {}//执行任务的接口run(), 这个方法对三个变量进行判断, 然后进行运算。 void run(){switch (op_){case '+':cout << data1_ << "+" << data2_ << "=" << data1_ + data2_ << endl;break;case '-':cout << data1_ << "-" << data2_ << "=" << data1_ - data2_ << endl;break;case '*':cout << data1_ << "*" << data2_ << "=" << data1_ * data2_ << endl;break;case '/':if (data2_ == 0){cout << "error, " << data1_ << '/' << data2_ << " is error!" << endl;}else{cout << data1_ << "/" << data2_ << "=" << data1_ / data2_ << endl;}break;case '%':if (data2_ == 0){cout << "error, " << data1_ << '%' << data2_ << " is error!" << endl;}else{cout << data1_ << "%" << data2_ << "=" << data1_ % data2_ << endl;}break;default:cout << "default" << endl;break;}}//仿函数, 为了方便我们的对象能够像函数一样使用。 void operator()(){run();}private://每一个任务对象里面都有三个参数, 一个data1, 一个data2, 最后一个op_int data1_;int data2_;char op_;
};

ThreadPool.h


#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;
#include<queue>
#include<ctime>
#include<unistd.h>//对线程的属性做一下封装, 有利于线程池的保存以及后面的处理
struct ThreadInfo
{pthread_t tid_;string name_;   
};template<class T>
class ThreadPool
{static const int defaultnum = 5;  //默认的线程池的大小(线程池的大小就是里面包含的线程的数量)private://加锁解锁void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}//唤醒线程, 线程是可以被挂起的(就比如信号量)。 当任务没有的时候,线程就要被挂起, 有任务后再唤醒void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}public://线程要执行的函数static void* HandlerTask(void* args){ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args);while(true) {tp->Lock();while (tp->tasks_.empty()){tp->ThreadSleep(); //如果队列里面没有任务了, 就让线程去休眠。}//否则就去拿到tasks里面的任务T t = tp->tasks_.front();tp->tasks_.pop();//tp->Unlock();t();  //每一个线程先对任务进行消费, 消费完成之后处理任务。    }}//运行这个线程池, 也就是先将线程创建出来。 然后去运行线程void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name_ = "thread-";threads_[i].name_ += to_string(i);   pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this);}}//主线程给线程池发送任务, 注意, 这个任务一定是可以被储存起来的。 因为当我们的任务很多很多的时候, 我们的线程池内的线程要一个一个地对这些任务进行处理void Push(const T& t){Lock();tasks_.push(t);Wakeup();Unlock();}//线程池地初始化, 就是将锁和条件变量都初始化一下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_);}private:vector<ThreadInfo> threads_;   //线程都维护在vector当中, 这个就是线程池里面的线程的个数,queue<T> tasks_ ;              //向线程池中发送任务, 这个队列里面保存的就是我们的任务的数目。 pthread_mutex_t mutex_;        //锁,用来生产者线程(本份代码只是主线程)给线程池发送任务时候加锁使用以及消费者线程抢夺任务时加锁使用 pthread_cond_t cond_;          //条件变量, 用来没有任务的时候,消费者要挂起。 };

main.cpp

#include"ThreadPool.h"
#include"Task.h"int main()
{//运行线程池ThreadPool<Task>* tp = new ThreadPool<Task>();tp->Start();    srand(time(nullptr) ^ getpid());while (true){//构建任务  int x = rand() % 10 + 1;usleep(10);int y = rand() % 5;char op = opers[rand()% opers.size()];Task t(x, y, op);//交给线程池处理//主线程给线程池发送任务, 其实就相当于主线程时生产者。 tp->Push(t);sleep(1);}return 0;
}

运行结果

单例模式

        利用单例模式来创建线程池

主要改动就是ThreadPool.h


#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<string>
using namespace std;
#include<queue>
#include<ctime>
#include<unistd.h>//对线程的属性做一下封装, 有利于线程池的保存以及后面的处理
struct ThreadInfo
{pthread_t tid_;string name_;   
};template<class T>
class ThreadPool
{static const int defaultnum = 5;  //默认的线程池的大小(线程池的大小就是里面包含的线程的数量)private://加锁解锁void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}//唤醒线程, 线程是可以被挂起的(就比如信号量)。 当任务没有的时候,线程就要被挂起, 有任务后再唤醒void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}public://线程要执行的函数static void* HandlerTask(void* args){ThreadPool<T>* tp = static_cast<ThreadPool<T>*>(args);while(true) {tp->Lock();while (tp->tasks_.empty()){tp->ThreadSleep(); //如果队列里面没有任务了, 就让线程去休眠。}//否则就去拿到tasks里面的任务T t = tp->tasks_.front();tp->tasks_.pop();//tp->Unlock();t();  //每一个线程先对任务进行消费, 消费完成之后处理任务。    }}//运行这个线程池, 也就是先将线程创建出来。 然后去运行线程void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name_ = "thread-";threads_[i].name_ += to_string(i);   pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this);}}//主线程给线程池发送任务, 注意, 这个任务一定是可以被储存起来的。 因为当我们的任务很多很多的时候, 我们的线程池内的线程要一个一个地对这些任务进行处理void Push(const T& t){Lock();tasks_.push(t);Wakeup();Unlock();}//线程池地初始化, 就是将锁和条件变量都初始化一下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_);}//获取单例//改编成单例的步骤里面只有这里要说一下, 就是为什么我们要套双层判断。 其实这里的最外面的一层的判断是我们另外加上去的。 为什么//要加这个判断呢? 就是如果我们不加最外层这一层判断。 那么每一个线程获取单例都要申请所,加锁。 不就是相当于所有的线程都在串行执行? 这就有效率问题。 //解决方案就是这个再加一层判断。 这样假如有四个线程。 那么一开始四个线程都在判断, 那么它们四个线程都进入了if里面。 然后就都申请锁, 但是只有第一个线程能够//进入第二层里面, 其他的进入不了。 那么当这一轮的四个线程都申请一次锁候就都退出了函数, 然后就都去做自己的事情了。 问题是, 当下次它们再来申请单例对象的时候它们连//第一层判断都成功不了了, 也就都不用加锁解锁了, 这就大大提高了效率!!!static ThreadPool<T>* GetInstance(int num = defaultnum){if (tp_ == nullptr){pthread_mutex__lock(&lock_);if (tp_ == nullptr) {tp_ = new ThreadPool<T>(num);}pthread_mutex_unlock(&lock_);}return tp_;}private://构造函数私有化, 只有Getinstance里面才能创建。 ThreadPool(int num = defaultnum):threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}//单例模式只有一个对象, 所以要将拷贝构造和拷贝赋值封住, 为了防止有人在外部重新拷贝一个对象。 ThreadPool(const ThreadPool<T>& tp) = delete;const ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete;~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}private:vector<ThreadInfo> threads_;   //线程都维护在vector当中, 这个就是线程池里面的线程的个数,queue<T> tasks_ ;              //向线程池中发送任务, 这个队列里面保存的就是我们的任务的数目。 pthread_mutex_t mutex_;        //锁,用来生产者线程(本份代码只是主线程)给线程池发送任务时候加锁使用以及消费者线程抢夺任务时加锁使用 pthread_cond_t cond_;          //条件变量, 用来没有任务的时候,消费者要挂起。 pthread_mutex_t lock_;         //锁, 这个锁是为了在获取单例的时候能够让线程原子性的访问if (tp_ == nullptr)。static ThreadPool<T>* tp_;    //tp指针, 这就是唯一个单例对象。 };template<class T>
ThreadPool<T>* ThreadPool<T>::tp_ = nullptr;    

 运行结果和上次一样:

常见的锁

  •         悲观锁:我们平时写的锁都是悲观锁。 就是总是担心其他线程来修改数据, 所以在线程访问数据之前都要先加锁。 就比如互斥锁和信号量。
  •         乐观锁:猜测线程在访问资源的时候不会有其他的线程,因此不上锁。但是在更新数据之前会判断其他线程在更新前有没有对数据进行修改。 
  •         CAS:当需要更新时,判断当前内存值和之前取得的值是否相等。 如果相等则用新值更新, 若不相等则失效。
  •         自旋锁:这个要着重谈一下, 后面很长一段论述都是在谈他。

自旋锁

在讲自旋锁之前, 博主要将在讲解进程等待的时候的故事再重新讲一下:       

  •         就是假设小王今天吃饭的时候想起来明天要考试了。 然后小王就很慌, 很慌怎么办呢。 所以小王就想到了他的好朋友小明。 小明是一个努力型学霸, 然后小王就找到他让他带他复习明天的考试, 小王请吃饭。 所以呢, 小明就很痛快地答应了。 但是小明要先读一个小时的书, 小明一听可以啊。 之后小明就去楼上读书了, 小王呢, 要知道我们现在是非常强调效率的!所以小王也不在原地等, 他说他去学校门口的网吧上会网, 然后他就去上网了。 小明读完书之后就给小王打了一个电话, 小王接到电话后立马又回来接着小明去吃饭了。——这是故事一
  •         在小明的帮助下, 小王疯狂的复习了一天一夜。 最后小王考试的时候成功考了60分, 过了。 几天之后小王又从别人的口中得知两天后又要考网络, 然后小王就又去请教小明。 小明就说, 好的, 马上我就从楼上下来, 我俩一起去自习室。 请问, 小王这个时候还回去网吧上网吗? 不会的!小王不去网吧了! 但是呢,过了十秒钟小王就给小明打了电话问下来了没有, 小明说马上。 又过了十秒钟, 小王又打电话问下来了没有, 小明说马上。 又过了十秒钟......, 过了很多个十秒钟之后, 小明终于下来了。 于是两个人愉快的又去吃饭了。 ——这是故事二。

        在上面的故事当中, 我们把小明当成临界区代码, 那么小王在楼下等小明和去网吧等小明取决于什么? 是不是就是取决于小明的时常? 我们前面一直都在研究临界区的线程安全问题。 但是我们几乎没有研究过线程在临界区待得时间的长短的问题。 等待什么呢? 等待刚刚进去的线程什么时候出来。 那么, 我们如果一个线程在临界区内待得时间非常长。 那么其他现车给在申请锁之后最好的做法是不是就是挂起? 如果一个线程在临界区待得时间非常短, 我们此时可以选择让其他线程在此时不要选择挂起, 而是处于一种自旋状态。 而我们的小王每十秒就打个电话的过程其实就是自选的过程。 所以我么以前学习的所有锁, 全部都是挂起等待锁。 一个线程进入, 其他的线程竞争失败, 失败的时候他们就把自己挂起等待了。 挂起等待要不要花时间? 挂起的时候就是将我们的执行流放到我们的等待队列里面, 然后唤醒的时候又把它们从等待队列里面拆下来放到cpu里面去执行。 这个过程来回对数据结构做迁移, 是要花时间的。 ——所以, 什么是自旋, 自旋就是不把自己挂起, 而是由线程不断地去周而复始的去申请锁。 如果申请锁成功则进入, 失败则返回重新检测锁的状态。 

自旋锁接口

        其实我们可以自己实现自旋锁, 就是使用trylock, trylock就是尝试检测这个锁, 如果这个锁没有申请成功, 就出错返回。 但是lock就是直接挂起, 所以我们想要实现自旋锁就可以使用while循环然后trylock。 

        也可以使用系统给我们提供的接口

        可以自己使用spin_lock, 这个函数就会对一个锁返回申请, 直到申请成功。 spin_trylock就是和mutex_trylock一样,都是申请失败了就直接返回, 想要自选需要自己加。

读者写者问题 

概念

        生活中有哪些场景是读者写者问题呢?

        我们在初中小学画的板报、作家写的小说、甚至博主写的这篇博客都是读者写者的问题。 就比如我们写一篇博客, 我们写博客的人就是写者, 然后读这篇博客的人就是读者。 

        对于读者写者问题来说, 也是要遵守321原则——三种关系、两种角色、一个交易场所。

  •         三种关系:写者和读者(互斥竞争)、读者和读者(共享关系)、写者和读者(互斥和同步)
  •         两种角色:写者和读者
  •         一个交易场所:数据交换的地点

        为什么读者和读者之间是共享关系, 为什么消费者和消费者之间确是互斥关系呢?

  •         因为写者把数据写进去了, 读者并不会把数据拿走!!!
  •         但是生产者生产了数据之后,消费者是会把数据拿走的!!!

 接口

pthread_rwlock_t是读写锁的类型, 然后上图两个函数就是对锁进行初始化和对锁进行销毁。

rdlock是以读者的身份进行加锁, 另外我们的wlock就是以写者的方式进行加锁。

 

然后呢, 我们的解锁时使用统一的接口进行解锁。

理解

        一般而言, 读多写少。 如果有一个写者在写, 任何读者都不能进来。 而一个读者正在读, 任何写者都不能进来。  所以, 正常来讲, 读者争锁的能力比写者要强。 所以在我们的读者写者当中, 我们往往会出现读者很多, 而写者很少的情况。 所以竞争锁的时候我们的读者竞争到锁的概率是非常的高的, 进而导致写者长时间得不到锁而产生饥饿问题。

        饥饿问题是一个中性的现象还是一个偏向编译的现象呢? 在读者写者模型这里是一个比较偏向中性的词语。 因为他就是一个事实, 因为读者本来就多, 读者本来就多写者本来就少, 所以就注定了根据这种场景是默认的现象。 那么这种默认的行为我们叫做读者优先。 

现在我们实现一下读者优先的伪代码

首先就是读者的加锁和解锁操作:
lock(&rlock);
reader_count++;
if (reader_count == 1) lock(&wlock);  //reader_count == 1说明这是第一个读者, 从这里开始就让读者进不来!!!
unlock(&rlock);//读者进来了之后, 就进行读取写者的数据
//都读取完成之后就离开
lock(&rlock);
reader_count--;
if (reader_count == 0) unlock(wlock); //如果是最后一个读者, 就让写者进来,就可以写数据了。 
unlock(&rlock);然后是写者的加锁和解锁,写者的比较简单,因为写者少, 竞争锁的能力弱
lock(&wlock);//写入unlock(&wlock);

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!   

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

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

相关文章

吴恩达深度学习笔记(6)

正交化 为了提高算法准确率&#xff0c;我们想到的方法 收集更多的训练数据增强样本多样性使用梯度下降将算法使算法训练时间更长换一种优化算法更复杂或者更简单的神经网络利用dropout 或者L2正则化改变网络框架更换激活函数改变隐藏单元个数 为了使有监督机制的学习系统良…

ansible playbooks

文章目录 一&#xff0c;ansible剧本二&#xff0c;ansible playbooks主要特性三&#xff0c;yaml基本语法规则四&#xff0c;剧本playbooks的组成结构五&#xff0c;yaml编写1.示例2.运行playbook2.1 运行2.2 检查yaml文件的语法是否正确2.3 检查tasks任务2.3 检查生效的主机2…

maven创建父子项目

创建父类 创建子模块 添加文件夹 配置tomcat 参考 然后启动项目即可 参考 https://blog.csdn.net/gjtao1130/article/details/115000022

Linux——shell 编程基础

基本介绍 shell 变量 环境变量&#xff08;也叫全局变量&#xff09; 位置参数变量 预定义变量 运算符 条件判断 流程控制 if 单分支&多分支 case 语句 for循环 while 循环 read 读取控制台输入 函数 系统函数 basename 获取文件名 dirname 获取目录路径 自定义函数 综…

DataWhale10月动手实践——Bot应用开发task03学习笔记

一、工作流 1. 工作流的定义 工作流由多个节点组成&#xff0c;这些节点可以包括大语言模型&#xff08;LLM&#xff09;、代码模块、逻辑判断工具、插件等。每个节点需要不同的信息来执行其功能。工作流的核心含义是&#xff1a;对工作流程及其操作步骤之间的业务规则进行抽…

中国信通院联合中国电促会开展电力行业企业开源典型实践案例征集

自2021年被首次写入国家“十四五”规划以来&#xff0c;开源技术发展凭借其平等、开放、协作、共享的优秀创作模式&#xff0c;正持续成为推动数字技术创新、优化软件生产模式、赋能传统行业转型升级、助力企业降本增效的重要引擎。电力是国民经济的重要基础性产业&#xff0c;…

开源神器!CodeFormer:一键去除马赛克,高清修复照片视频

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; 微信公众号&#xff5c;搜一搜&…

Docker安装Mysql数据库

不同的应用程序可能依赖于不同版本的 MySQL 或具有不同的配置需求。通过 Docker&#xff0c;每个 MySQL 实例都可以运行在独立的容器中&#xff0c;与宿主机以及其他容器的环境相互隔离。这有效避免了因不同应用对 MySQL 版本、依赖库等方面的差异而导致的冲突。例如&#xff0…

盛元广通数字化实验动物中心LIMS综合管理系统

盛元广通数字化实验动物中心LIMS综合管理系统通过集成各种功能&#xff0c;从实验申请、伦理审批、笼位预约、动物采购到开展动物实验、数据归档等全流程智能化管理&#xff0c;保证了实验信息随时可查&#xff0c;管理可视化、流程简单化。实验动物中心采用电脑端、APP和微信小…

LangSplat和3D language fields简略介绍

LangSplat: 3D Language Gaussian Splatting 相关技术拆分解释&#xff1a; 3dgs&#xff1a;伟大无需多言SAM&#xff1a;The Segment Anything Model&#xff0c;是图像分割领域的foundational model&#xff0c;已经用在很多视觉任务上&#xff08;如图像修复、物体追踪、图…

Linux目录

一、虚拟机环境配置 1.安装虚拟机 安装步骤 新建虚拟机-->典型安装-->选择稍后安装操作系统-->选择系统类型和版本&#xff08;这里安装的是CentOS7 64位&#xff09;-->选择虚拟机文件路径&#xff08;建议每台虚拟机单独存放并且路径不要有中文&#xff09;--&…

商淘云连锁管理系统

商淘云连锁管理系统助力连锁企业实现“人货账”全方位数字化管理&#xff0c;它依托连锁品牌进销存管理实现门店订货、线下收银、线上商城、会员营销等一体化管理。 门店订货补货支持连锁直营、加盟 不同门店不同进货价、不同门店不同商品、不同门店在线或者账期支付、门店PC或…

Go语言Gin框架的常规配置和查询数据返回json示例

文章目录 路由文件分组查询数据库并返回jsonservice层controller路由运行效果 启动多个服务 在 上一篇文章《使用Go语言的gorm框架查询数据库并分页导出到Excel实例》 中主要给大家分享了较多数据的时候如何使用go分页导出多个Excel文件并合并的实现方案&#xff0c;这一篇文章…

跨界创新|使用自定义YOLOv11和Ollama(Llama 3)增强OCR文本识别

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Electron-(三)网页报错处理与请求监听

在前端开发中&#xff0c;Electron 是一个强大的框架&#xff0c;它允许我们使用 Web 技术构建跨平台的桌面应用程序。在开发过程中&#xff0c;及时处理网页报错和监听请求是非常重要的环节。本文将详细介绍 Electron 中网页报错的日志记录、webContents 的监听事件以及如何监…

CTFHUB技能树之XSS——过滤关键词

开启靶场&#xff0c;打开链接&#xff1a; 看上去跟上一题应该差不多&#xff0c;应该只是添加多点过滤规则吧 直接拿xss平台的代码试试&#xff1a; <sCRiPt sRC//xs.pe/6b6></sCrIpT> 这时候突然听到xss平台的上线语音提醒&#xff1a; 成功得到flag&#xff1…

react里实现左右拉伸实战

封装组件&#xff1a; 我自己写的一个简单的组件&#xff0c;可能有bug。不想自己写&#xff0c;建议用第三方库实现。 新建一个resizeBox.tsx文件写上代码如下&#xff1a; import React, { ReactNode, useState, useEffect, useRef } from react; import styles from &quo…

具备哪些特质的内外网文件交换系统 才是高科技企业需要的?

高科技企业是指涉及对国家产生深远和积极影响的先进技术的产业集群&#xff0c;它们以持续的创新和高研发投入为核心&#xff0c;推动科技进步和产业升级。高科技企业是市场经济的重要组成&#xff0c;为经济发展和技术进步提供充足动力&#xff0c;因此&#xff0c;高科技企业…

LeetCode刷题日记之贪心算法(五)

目录 前言无重叠区间划分字母区间合并区间单调递增的数字监控二叉树总结 前言 随着对贪心算法的不断深入&#xff0c;本篇文章将继续挑战一些经典的题目&#xff0c;进一步巩固这一算法的应用技巧。希望博主记录的内容能够帮助大家更好地掌握贪心算法的解题思路✍✍✍ 无重叠区…

【K8S系列】Kubernetes Pod节点CrashLoopBackOff 状态及解决方案详解【已解决】

在 Kubernetes 中&#xff0c;Pod 的状态为 CrashLoopBackOff 表示某个容器在启动后崩溃&#xff0c;Kubernetes 尝试重启该容器&#xff0c;但由于持续崩溃&#xff0c;重启的间隔时间逐渐增加。下面将详细介绍 CrashLoopBackOff 状态的原因、解决方案及相关命令的输出解释。 …