【Linux笔记】——线程池项目与线程安全单例模式

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹: 【Linux笔记】——简单实习一个日志项目
🔖流水不争,争的是滔滔不息


  • 一、线程池设计
  • 二、线程池代码
  • 三、线程安全的单例模式
  • 四、线程安全和重入问题

一、线程池设计

线程池

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度,可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。线程池的主要优点是减少在创建和销毁线程上所花的时间以及系统资源的开销。通过重用已存在的线程,线程池可以显著提高系统性能,特别是在需要处理大量短生命周期任务的场景中。

使用场景

需要大量的线程来完成任务,且完成任务的时间比较短。比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

对性能要求苛刻的应用,比如要求服务器迅速响应客户的请求。

接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

在这里插入图片描述

二、线程池代码

ThreadPool.hpp线程池主体逻辑

#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include "Log.hpp"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
using namespace std;
using namespace MutexModule;
using namespace CondModule;
using namespace ThreadModule;
using namespace LogModule;const int gnum=5;
namespace ThreadPoolModule
{template<class T>class ThreadPool{public:ThreadPool(int num=gnum):_num(num),_isrunning(false),_sleepnum(0){for(int i=0;i<_num;i++){_thread.emplace_back([this](){HandlerTask();});}}void Threadone(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠线程";}void Threadall(){LockGuard lockguard (_mutex);if(_sleepnum>0){_cond.Broadcast();}LOG(LogLevel :: INFO)<<"唤醒所有休眠线程";}void Start(){if(_isrunning) return;_isrunning=true;for(auto &thread :_thread){thread.Start();}LOG(LogLevel :: INFO)<<"开始创建线程池";}void Stop(){if(!_isrunning) return;_isrunning =false;Threadall();//让等待的进程全部启动}void Join(){for(auto &thread :_thread){thread.Join();}LOG(LogLevel :: INFO)<<"线程回收";}void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while(true){T t;{LockGuard lockguard (_mutex);if(_taskq.empty() && _isrunning) //把全部休眠的线程启动,必须保证进程池已经退出状态,要不就陷入死循环{_sleepnum++;//如果等待计数++_cond.Wait(_mutex);_sleepnum--;//退出等待计数--}if(_taskq.empty() && !_isrunning) //等待后唤醒,必须是任务队列为空 进程池退出{LOG(LogLevel :: INFO)<<name<<"退出了,任务队列为空,进程池退出";break;}t=_taskq.front();_taskq.pop();}t();//执行任务}}bool Enqueue (const T& in){if(_isrunning){LockGuard LockGuard (_mutex);_taskq.push(in);if(_sleepnum==_thread.size()){Threadone();}return true;}return false;    }~ThreadPool(){}private:vector<Thread> _thread;queue<T> _taskq;int _num;Mutex _mutex;Cond _cond;bool _isrunning;int _sleepnum;};
}

这里用到了之前封装好的线程、条件变量互斥与同步、日志。

私有成员变量
_thread我们用vector数组充当线程池,_taskq任务队列用的是queue队列,_num是线程池中的线程的个数(我们写的是固定线程的线程池),_isrunning判断线程是否运行,_sleepnum是线程等待的个数。


		ThreadPool(int num=gnum):_num(num),_isrunning(false),_sleepnum(0){for(int i=0;i<_num;i++){_thread.emplace_back([this](){HandlerTask();});}}void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while(true){T t;{LockGuard lockguard (_mutex);if(_taskq.empty() && _isrunning) //把全部休眠的线程启动,必须保证进程池已经退出状态,要不就陷入死循环{_sleepnum++;//如果等待计数++_cond.Wait(_mutex);_sleepnum--;//退出等待计数--}if(_taskq.empty() && !_isrunning) //等待后唤醒,必须是任务队列为空 进程池退出{LOG(LogLevel :: INFO)<<name<<"退出了,任务队列为空,进程池退出";break;}t=_taskq.front();_taskq.pop();}t();//执行任务}

上面的代码是构造函数构造线程池,这里主要阐述,构造函数创建线程池与线程去执行任务的函数的关系。
构造函数通过一个for循环,我们创建了_num个Thread对象,每个对象都绑定一个lambda,lambda里面调用的是线程池的成员函数HandlerTask()(在类内调用类内成员函数用lambda),这些lambda是“线程的入口函数”,它们一启动就跑进HandlerTask()中并一直在那里循环干活。


void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while(true){T t;{LockGuard lockguard (_mutex);if(_taskq.empty() && _isrunning) //把全部休眠的线程启动,必须保证进程池已经退出状态,要不就陷入死循环{_sleepnum++;//如果等待计数++_cond.Wait(_mutex);_sleepnum--;//退出等待计数--}if(_taskq.empty() && !_isrunning) //等待后唤醒,必须是任务队列为空 进程池退出{LOG(LogLevel :: INFO)<<name<<"退出了,任务队列为空,进程池退出";break;}t=_taskq.front();_taskq.pop();}t();//执行任务}

上面这段代码。一启动,线程池的每个线程都会进去,所以加锁。如果任务队列是空的并且这个线程池已经启动了,那么线程就要进入等待队列(条件变量同步),注意要写一个计数的变量需要进入等待队列就要++出来就–(这里+±-就是为了唤醒休眠线程的时候进行判断,颗粒度更细)。如果任务队列为空并且线程池也不在运行了,直接break退出回收线程。最后满足线程能拿到任务,取出队首的任务,执行任务。


		//唤醒单个线程void Threadone(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠线程";}//唤醒所有进程void Threadall(){LockGuard lockguard (_mutex);if(_sleepnum>0){_cond.Broadcast();}LOG(LogLevel :: INFO)<<"唤醒所有休眠线程";}//创建线程池void Start(){if(_isrunning) return;_isrunning=true;for(auto &thread :_thread){thread.Start();}LOG(LogLevel :: INFO)<<"开始创建线程池";}//终止线程void Stop(){if(!_isrunning) return;_isrunning =false;Threadall();//让等待的进程全部启动}//回收线程void Join(){for(auto &thread :_thread){thread.Join();}LOG(LogLevel :: INFO)<<"线程回收";}//任务队列中放任务bool Enqueue (const T& in){if(_isrunning){LockGuard LockGuard (_mutex);_taskq.push(in);if(_sleepnum==_thread.size()){Threadone();}return true;}return false;    }

唤醒单个线程直接调用之前封装好的条件变量同步,唤醒所有休眠的线程,如果之前计数的_sleepnum>0就要唤醒所有的休眠线程了。
启动线程就是创建vector数组里的线程,创建线程池。
进程终止,调用之前封装的条件变量。
回收线程,调用之前封装的条件变量。

Enqueue就是往任务队列里放任务,if(_sleepnum==_thread.size()) 判断是否所有线程都在休眠,避免唤醒线程池中已经在忙的线程(节省上下文切换开销)如果不判断 _sleepnum,直接 Threadone() 会怎样?可能会导致重复唤醒甚至无意义的上下文切换。比如:有5个线程,其中2个在处理任务,3个在睡觉;你来了个新任务,就唤醒1个线程;但其实可能原本某个线程马上就处理完会抢新任务;你提前唤醒一个,就多了一次线程上下文切换(白唤醒了)。


main.cc

#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"using namespace LogModule;
using namespace ThreadPoolModule;int main()
{Enable_Console_Log_Strategy();int cnt=5;ThreadPool<task_t>* tp=new ThreadPool <task_t> ();tp->Start();while(cnt){sleep(1);tp->Enqueue(Download);cnt--;}tp->Stop();tp->Join();return 0;
}

Task.hpp

#pragma once
#include <iostream>
#include <functional>
#include "Log.hpp"using namespace LogModule;using task_t = std::function<void()>;void Download()
{LOG(LogLevel::DEBUG) << "我是一个下载任务...";
}

这里的function就简单写了一下,没有放具体的任务,但是要走到这里用的是function的语法


线程池就是通过一个vector数组(这里这么写的也可以是别的)里面创建线程,其实就是把线程准备好放在vector数组中,任务来了线程直接就能用,大大提高了效率。线程池就是提前创建一批线程放在池子里反复复用,避免任务来了才临时创建/销毁线程造成的高开销。

在这里插入图片描述

常规线程池:源码

三、线程安全的单例模式

单例模式

单例模式是一种设计模式,确保一个类只有一个实例(对象),并提供一个全局访问点来获取该实例。这种模式通常用于控制资源的访问,例如数据库连接、日志记录器等,以避免创建多个实例导致资源浪费或冲突。

饿汉模式实现单例
一开始就创建好单例对象。程序一启动,就创建好对象了,饿的不行

懒汉模式实现单例
什么时候用什么时候创建单例对象。防止创建多个对象。第一次用对象是才创建,是不是很懒。


线程安全的懒汉式单例模式

禁用拷贝构造和赋值重载

ThreadPool(const ThreadPool<T> &) = delete;               // 把拷贝构造给禁用 没办法直接创建对象
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // 把赋值重载禁用

显示的禁掉拷贝构造和赋值重载,单例模式的最大的特征以及核心就是,保证全程只有一个对象。如果不禁掉拷贝构造和赋值重载就会出现多个对象,完全违背了单例迷失的初衷。
没有禁用拷贝构造和赋值重载,单例根本不单例。


类外初始化单例指针

template <class T>
class ThreadPool {
public:static ThreadPool<T>* inc;// 其他成员...
};template <class T>
ThreadPool<T>* ThreadPool<T>::inc = nullptr;// 类外初始化template <class T>
Mutex ThreadPool<T>::_lock; // 类外初始化

类内声明的静态成员变量,必须类外初始化。 static ThreadPool* inc这个静态成员变量必须类外初始化。原因是:静态成员变量属于类本身,而不是某个对象。类声明只是告诉编译器"这里有这么个静态变量",但不分配内存。只有在类外定义后,编译器才会给它分配内存空间。

为什么要加static成为静态成员变量?


创建单例对象

static ThreadPool<T> *GetInstance(){if (inc == nullptr) // 多加一层多一层保护  {LockGuard lockguard(_lock); // 为防止多线程访问,加锁if (inc == nullptr) //第一次用这个inc单例指针对象{LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";inc = new ThreadPool<T>();  //创建单例指针对象}}return inc;}

这里主要就是创建单例指针对象,如果这里不加static如果想调用这个函数是不是就要创建这个类的对象就违背了单例的初衷。所以这里要让它成为静态成员函数。这也回答了上面为什么要用static成员变量,在这个静态成员函数中创建单例指针对象,要使用静态成员变量。
内层中的if (inc == nullptr)是判断是否是第一次用这个inc单例指针对象,如果没有创建单例指针对象。加锁是为了防止多线程访问。在外层的if (inc == nullptr),首先不加外层的这个if (inc == nullptr),线程来了访问互斥锁,拿到锁的线程进去创建单例指针对象,那么多线程是不是每次不管是否已经创建了单例指针对象都要拿锁然后进去转一圈在返回这个单例指针对象,是不是效率就会降低。加上最外侧的if (inc == nullptr) 每次线程来了,如果已经有单例指针对象了就不要进去在转一圈,直接返回对象就完了,大大提高了效率。


Main.cc

#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"using namespace LogModule;
using namespace ThreadPoolModule;int main()
{Enable_Console_Log_Strategy();//int cnt=5;// ThreadPool<task_t>* tp=new ThreadPool <task_t> ();ThreadPool<task_t> ::GetInstance()->Start();while(cnt){sleep(1);ThreadPool<task_t> ::GetInstance()->Enqueue(Download);cnt--;}ThreadPool<task_t> ::GetInstance()->Stop();ThreadPool<task_t> ::GetInstance()->Join();return 0;
}

单例模式如何访问类内成员函数呢?不用创建类的对象,直接用作用域解释符,调用静态成员函数。这样获取进程池的唯一对象,然后通过指针调用方法创建线程池。

懒汉式单例模式线程池

#pragma once#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include "Log.hpp"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
using namespace std;
using namespace MutexModule;
using namespace CondModule;
using namespace ThreadModule;
using namespace LogModule;const int gnum = 5;
namespace ThreadPoolModule
{template <class T>class ThreadPool{private:ThreadPool(int num = gnum): _num(num), _isrunning(false), _sleepnum(0){for (int i = 0; i < _num; i++){_thread.emplace_back([this](){ HandlerTask(); });}}void Threadone(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠线程";}void Threadall(){LockGuard lockguard(_mutex);if (_sleepnum > 0){_cond.Broadcast();}LOG(LogLevel ::INFO) << "唤醒所有休眠线程";}ThreadPool(const ThreadPool<T> &) = delete;               // 把拷贝构造给禁用 没办法直接创建对象ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // 把赋值重载禁用public:static ThreadPool<T> *GetInstance(){if (inc == nullptr) // 多加一层多一层保护  {LockGuard lockguard(_lock); // 为防止多线程访问,加锁if (inc == nullptr) //第一次用这个inc单例指针对象{LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";inc = new ThreadPool<T>();  //创建单例指针对象}}return inc;}void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _thread){thread.Start();}LOG(LogLevel ::INFO) << "开始创建线程池";}void Stop(){if (!_isrunning)return;_isrunning = false;Threadall(); // 让等待的进程全部启动}void Join(){for (auto &thread : _thread){thread.Join();}LOG(LogLevel ::INFO) << "线程回收";}void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;{LockGuard lockguard(_mutex);if (_taskq.empty() && _isrunning) // 把全部休眠的线程启动,必须保证进程池已经退出状态,要不就陷入死循环{_sleepnum++; // 如果等待计数++_cond.Wait(_mutex);_sleepnum--; // 退出等待计数--}if (_taskq.empty() && !_isrunning) // 等待后唤醒,必须是任务队列为空 进程池退出{LOG(LogLevel ::INFO) << name << "退出了,任务队列为空,进程池退出";break;}t = _taskq.front();_taskq.pop();}t(); // 执行任务}}bool Enqueue(const T &in){if (_isrunning){LockGuard LockGuard(_mutex);_taskq.push(in);if (_sleepnum == _thread.size()){Threadone();}return true;}return false;}~ThreadPool(){}private:vector<Thread> _thread;queue<T> _taskq;int _num;Mutex _mutex;Cond _cond;bool _isrunning;int _sleepnum;static ThreadPool<T> *inc;static Mutex _lock;};template <class T>ThreadPool<T> *ThreadPool<T>::inc = nullptr; // 类外初始化template <class T>Mutex ThreadPool<T>::_lock; // 类外初始化
}

运行结果
在这里插入图片描述
懒汉式单例模式线程池:源码

四、线程安全和重入问题

线程安全:就是多个线程在访问共享资源时,能够正确地执行,不会相互干扰或破坏彼此的执行结果。⼀般而言,多个线程并发同一段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进行操作,并且没有锁保护的情况下,容易出现该问题。

重入:同⼀个函数被不同的执行流调用,当前⼀个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。⼀个函数在重⼊的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。学到现在,其实我们已经能理解重入其实可以分为两种情况1.多线程重入函数2.信号导致⼀个执行流重复进入函数


可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的(其实知道这⼀句话就够了)
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果⼀个函数中有全局变量,那么这个函数既不是线程安全也不是可重人的。

可重入与线程安全区别

  • 可重入函数是线程安全函数的⼀种
  • 线程安全不一定是可重入的,而可重入函数则⼀定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

如果不考虑信号导致一个执行流重复进入函数这种重入情况,线程安全和重入在安全角度不做区分。
但是线程安全侧重说明线程访问公共资源的安全情况,表现的是并发线程的特点。
可重入描述的是一个函数是否被重复进入,表示的是函数特点。

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

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

相关文章

28-FreeRTOS内核控制-延时-临界区

一、FreeRTOS的内核控制接口分析 1.1 函数taskYIELD 此函数用于进行任务切换&#xff0c;此函数本质上是一个宏。它允许当前任务主动放弃CPU使用权&#xff0c;将控制权转移给调度器&#xff0c;以便调度器可以选择另一个就绪任务运行。taskYIELD通常用于协作式多任务系统中&am…

NtfsLookupAttributeByName函数分析之和Scb->AttributeName的关系

第一部分&#xff1a; VOID FindFirstIndexEntry ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN PVOID Value, IN OUT PINDEX_CONTEXT IndexContext ) { 。。。。。。 // // Lookup the attribute record from the Scb. // if (!NtfsLookupAt…

关闭 Ubuntu 20.04 的 GNOME Shell和PulseAudio

一、GNOME Shell GNOME Shell 是 Ubuntu 20.04 默认的桌面环境管理器。关闭它会失去图形界面&#xff08;回到纯终端模式&#xff09;&#xff0c;但可以节省内存和 CPU 资源。 方法 1&#xff1a;临时关闭&#xff08;当前会话生效&#xff09; sudo systemctl stop gdm #…

Dijkstra算法——不带负权的单源最短路径

目录 算法学习 算法原理 稠密图Dijkstra模板 稀疏图Dijkstra模板 练习 1 网络延迟时间 2 到达最后一个房间的最少时间Ⅰ 3 到达最后一个房间的最少时间Ⅱ 4 访问消失节点的最少时间 5 设计可以求最短路径的图类 6 概率最大的路径 7 最小体力消耗路径 8 从第一个节…

【安全攻防与漏洞​】​​Heartbleed漏洞复现与修复

Heartbleed漏洞复现与修复 一、漏洞原理 Heartbleed漏洞&#xff08;CVE-2014-0160&#xff09; 是 OpenSSL 1.0.1 至 1.0.1f 版本中的一个严重内存泄漏漏洞。它源于 TLS 心跳扩展&#xff08;Heartbeat Extension&#xff09;协议中对请求长度字段的未校验&#xff0c;导致攻…

力扣-最大连续一的个数

1.题目描述 2.题目链接 1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09; 3.代码解答 class Solution {public int longestOnes(int[] nums, int k) {int zero0,length0;for(int left0,right0;right<nums.length;right){if(nums[right]0){zero;}while…

虚拟机Centos7:Cannot find a valid baseurl for repo: base/7/x86_64问题解决

问题 解决&#xff1a;更新yum仓库源 # 备份现有yum配置文件 sudo cp -r /etc/yum.repos.d /etc/yum.repos.d.backup# 编辑CentOS-Base.repo文件 vi /etc/yum.repos.d/CentOS-Base.repo[base] nameCentOS-$releasever - Base baseurlhttp://mirrors.aliyun.com/centos/$relea…

Node.js 库大全

在当今快速迭代的软件开发领域&#xff0c;Node.js 凭借其强大的异步 I/O 处理能力和繁荣的生态系统&#xff0c;已成为全栈开发的核心技术。社区中涌现的无数实用库&#xff0c;如同开发者手中的“瑞士军刀”&#xff0c;能显著提升效率、优化性能并保障安全。本文将系统梳理 …

如何评估物联网框架的交互体验?

物联网&#xff08;IoT&#xff09;技术的快速发展推动了各类物联网框架的涌现&#xff0c;但如何评估其交互体验却成为开发者和企业面临的重要挑战。交互体验不仅涉及用户界面&#xff08;UI&#xff09;的直观性&#xff0c;还包括设备接入效率、协议兼容性、数据交互流畅度以…

3D个人简历网站 6.弹出框

3D个人简历网站 6.弹出框 在components下创建HomeInfo.jsx用于控制主页弹出框信息 输入rafce快速生成代码块 import React from reactconst HomeInfo () > {return (<div>HomeInfo</div>) }export default HomeInfo修改Home.jsx代码实现弹出简单效果 ……re…

在 ABP VNext 中集成 OpenCvSharp:构建高可用图像灰度、压缩与格式转换服务

&#x1f680; 在 ABP VNext 中集成 OpenCvSharp&#xff1a;构建高可用图像灰度、压缩与格式转换服务 &#x1f389; &#x1f4da; 目录 &#x1f680; 在 ABP VNext 中集成 OpenCvSharp&#xff1a;构建高可用图像灰度、压缩与格式转换服务 &#x1f389;&#x1f3af; 一、…

C++之STL--string

string 深入探索 C STL 中的 std::string一、std::string 的基本概念1. 内存管理2. 安全性 二、std::string 的构造与初始化1. 默认构造2. 从 C 风格字符串构造3. 从字符串的一部分构造4. 使用重复字符构造 三、std::string 的常用操作1. 字符串拼接2. 字符串比较3. 字符串查找…

网络层——蚂蚁和信鸽的关系VS路由原理和相关配置

前言&#xff08;&#x1f41c;✉️&#x1f54a;️&#xff09; 今天内容的主角是蚂蚁&#xff08;动态路由&#xff09;和信鸽&#xff08;静态路由&#xff09;&#xff0c;为什么这么说呢&#xff0c;来看一则小故事吧。 森林里&#xff0c;森林邮局要送一份重要信件&am…

在 Excel xll 自动注册操作 中使用东方仙盟软件2————仙盟创梦IDE

// 获取当前工作表名称string sheetName (string)XlCall.Excel(XlCall.xlfGetDocument, 7);// 构造动态名称&#xff08;例如&#xff1a;Sheet1!MyNamedCell&#xff09;string fullName $"{sheetName}!MyNamedCell";// 获取引用并设置值var namedRange (ExcelRe…

nginx日志

目录 实验要求&#xff1a; 实验1&#xff1a; 1.使用vim打开/etc/nginx/nginx.conf查看内容 2.重新读取文件并且重启软件 3.实时查看nginx日志 实验2&#xff1a; 1.使用vim打开/etc/rsyslog.conf 2.配置此文件 3.保存退出后&#xff0c;将核心防护与防火墙关闭。 4.…

【高德开放平台-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

2024 CKA模拟系统制作 | Step-By-Step | 3、CKA考试系统的技术设置

目录 免费获取题库配套 CKA_v1.31_模拟系统 一、免费提权配置 1、使用vim 编辑/etc/sudoers 二、安装命令 1、安装运行时接口命令 2、安装Etcd命令 3、配置K8S命令自动补全 三、配置Kubectl 访问集群 1、Master节点 2、Node01节点 四、SSH配置 1、Node01节点candi…

微信小程序请求扣子(coze)api的例子

1. 准备工作 在开始之前&#xff0c;确保已经完成了以下准备工作&#xff1a; 创建并发布了 Coze 智能体。获取了个人访问令牌&#xff08;Personal Access Token&#xff09;&#xff0c;这是用于授权的关键凭证。确认目标智能体的 Bot ID 和其他必要参数已准备就绪。 2. 请…

visual studio重新安装如何修改共享组件、工具和SDK路径方案

安装了VsStudio后,如果自己修改了Shared路径&#xff0c;当卸载旧版本&#xff0c;需要安装新版本时发现&#xff0c;之前的Shared路径无法进行修改&#xff0c;这就很坑了 但是却遇到了路径无法修改的问题…真让人头大&#xff0c;当然不修改也可以&#xff0c;有时候&#x…

【Python 算法零基础 4.排序 ② 冒泡排序】

目录 一、引言 二、算法思想 三、时间复杂度和空间复杂度 1.时间复杂度 2.空间复杂度 四、冒泡排序的优缺点 1.算法的优点 2.算法的缺点 五、实战练习 88. 合并两个有序数组 算法与思路 ① 合并数组 ② 冒泡排序 2148. 元素计数 算法与思路 ① 排序 ② 初始化计数器 ③ 遍历数组…