Linux -日志 | 线程池 | 线程安全 | 死锁

文章目录

    • 1.日志
      • 1.1日志介绍
      • 1.2策略模式
      • 1.3实现日志类
    • 2.线程池
      • 2.1线程池介绍
      • 2.2线程池的应用场景
      • 2.3线程池的设计
      • 2.4代码实现
      • 2.5修改为单例模式
    • 3.线程安全和函数重入问题
      • 3.1线程安全和函数重入的概念
      • 3.2总结
    • 4.死锁
      • 4.1什么是死锁
      • 4.2产生死锁的必要条件
      • 4.3避免死锁


1.日志

1.1日志介绍

计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信
息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯ 具。
⽇志格式以下⼏个指标是必须得有的
• 时间戳
• 日志等级
• 日志内容
以下几个指标是可选的 • 文件名行号 • 进程,线程相关id信息等。

1.2策略模式

1.2.1介绍

策略模式(Strategy Pattern)是一种对象行为型设计模式,它定义了一系列算法,并将每个算法封装在独立的类中,使得它们可以相互替换。这样,算法的变化就不会影响到使用算法的客户端代码,从而提高了代码的灵活性和可维护性。

1.2.2策略模式的结构和角色

策略模式通常由以下几个角色组成:

  1. 抽象策略类(Strategy):

    • 定义了一个公共接口,用于封装具体的算法。

    • 不同的具体策略类会实现这个接口,提供不同的算法实现。

  2. 具体策略类(Concrete Strategy):

    • 实现了抽象策略类定义的算法接口,具体实现了具体的算法逻辑。

    • 每个具体策略类都代表了一种具体的算法或策略。

  3. 上下文类(Context):

    • 持有一个策略类的引用,在客户端代码中通过该引用调用具体策略的方法。

    • 上下文类还可以维护一些公共的状态或行为,这些状态或行为可以在不同的策略之间共享。

1.2.3策略模式的优点

  1. 提高了代码的灵活性和可维护性:

    • 由于算法被封装在独立的策略类中,因此可以方便地添加、删除或修改算法,而不需要修改客户端代码。

    • 这使得系统更加易于维护和扩展。

  2. 遵循了开闭原则:

    • 策略模式允许在不修改现有代码的情况下添加新的策略类,从而实现了对扩展的开放和对修改的关闭。

  3. 减少了条件语句的使用:

    • 在不使用策略模式的情况下,客户端代码中可能会包含大量的条件语句来根据不同的算法进行选择。

    • 而使用策略模式后,这些条件语句可以被封装在策略类中,客户端只需要选择合适的策略类进行调用即可。

  4. 实现了算法的定义、选择和使用的分离:

    • 策略模式将算法的定义、选择和使用分离开来,使得算法可以独立变化,而不影响使用算法的客户端代码。

1.2.4策略模式的缺点

  1. 增加了系统中的类和对象数量:

    • 由于每个具体策略类都需要一个单独的类进行实现,因此这可能会增加系统的复杂性。

  2. 客户端需要了解不同的策略类:

    • 客户端需要了解并选择合适的策略类进行调用,这可能会增加客户端的复杂性。

  3. 可能导致客户端代码变得复杂

1.3实现日志类

1.3.1实现的格式

[具体时间] [日志的类型] [进程id] [源文件名][行号] 其他内容 

1.3.2策略

采用策略模拟实现,实现输出到屏幕和输出到文件两种策略。

1.3.3代码实现

//Mutex.hpp -- 互斥锁#pragma once
#include <iostream>
#include <pthread.h>namespace LockModule
{class Mutex{Mutex(const Mutex &) = delete;const Mutex &operator=(const Mutex &) = delete;public:Mutex(){int n = pthread_mutex_init(&_lock, nullptr); // 初始化锁(void)n;                                     // 防止报警告}// 上锁void Lock(){int n = pthread_mutex_lock(&_lock);(void)n;}// 返回锁指针pthread_mutex_t *LockPtr(){return &_lock;}// 解锁void Unlock(){int n = pthread_mutex_unlock(&_lock);(void)n;}~Mutex(){int n = pthread_mutex_destroy(&_lock);(void)n;}private:pthread_mutex_t _lock;};//二次封装 -- 这样可以不用解锁了class LockGuard{public:LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mutex &_mtx;};}/
//log.hpp --日志#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem>
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"namespace LogMudule
{using namespace LockModule;// 获取时间 格式 年-月-日 时-分-秒std::string CurrentTime(){// 获取时间搓time_t _time = time(nullptr);// 转化为具体时间struct tm curr;localtime_r(&_time, &curr);// 转化为字符串char buff[1024];snprintf(buff, sizeof(buff), "%d-%d-%d %d-%d-%d",curr.tm_year + 1900, // 从1900开始算curr.tm_mon + 1,     // 从0开始算curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buff;}// 日志文件的默认路径和文件名const std::string defaultlogpath = "./";const std::string defaultlogname = "log.txt";// 日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};// 映射日志等级std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNIGN";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:break;}//不存在return "None";}// 刷新策略.class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0; // 纯虚函数};// 输出到屏幕策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}~ConsoleLogStrategy(){}void SyncLog(const std::string &message)    //message - 日志内容{// 加锁 -- 防止乱序LockGuard _lock(_mutex);std::cout << message << std::endl;}private:Mutex _mutex;};// 文件级(磁盘)策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname): _logpath(logpath),_logname(logname){// 加锁 -- 线程安全LockGuard _lock(_mutex);// 当前目录存在if (std::filesystem::exists(_logpath)){return;}// 不存在则创建目录try{std::filesystem::create_directories(_logpath);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << "\n";}}// 向磁盘文件输出void SyncLog(const std::string &message) //message - 日志内容{// 加锁 -- 防止乱序LockGuard _lock(_mutex);// 以最加式打开文件std::string log = _logpath + _logname;std::ofstream _ofs(log, std::ios::app);if (!_ofs.is_open()){return;}_ofs << message << '\n';_ofs.close();}~FileLogStrategy(){}private:std::string _logpath;std::string _logname;// 锁Mutex _mutex;};// 日志类 - 根据刷新策略进行刷新class Logger{public:Logger(){// 默认向屏幕刷新_strategy = std::make_shared<ConsoleLogStrategy>();}// 设置向屏幕输出void EnableConsoleLog(){_strategy = std::make_shared<ConsoleLogStrategy>();}// 设置向文件输出void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}~Logger() {}//内部类class LogMessage{public://初始化成员变量LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()),_level(level),_pid(getpid()),_filename(filename),_line(line),_logger(logger){// 将数据构成一条完整的字符串std::stringstream ssbuff;ssbuff << "[" << _currtime << "]"<< "[" << Level2String(_level) << "]"<< "[" << _pid << "]"<< "[" << _filename << "]"<< "[" << _line << "]";_loginfo = ssbuff.str();}template <typename T>LogMessage &operator<<(const T &info){// 添加字符串内容std::stringstream s;s << info;_loginfo += s.str();return *this;}//在析构时调用~LogMessage(){if (_logger._strategy != nullptr){_logger._strategy->SyncLog(_loginfo);}}private:std::string _currtime; // 当前日志的时间LogLevel _level;       // 日志等级pid_t _pid;            // 进程pidstd::string _filename; // 源文件名称int _line;             // 日志所在的行号Logger &_logger;       // 负责根据不同的策略进行刷新std::string _loginfo;  // 一条完整的日志记录};// 就是要拷贝,故意的拷贝-- 这样执行完语句自动销毁就会调用析构实行策略LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案};//实例
Logger logger;//使用宏替换
#define LOG(Level) logger(Level, __FILE__, __LINE__)    // 调用重载()  __FILE__, __LINE__ -- 获取源文件名和当前行号//切换策略
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()  
#define ENABLE_FILE_LOG() logger.EnableFileLog()}
//Main.cc
#include"log.hpp"using namespace LogMudule;int main()                                              
{ENABLE_CONSOLE_LOG()LOG(LogLevel::DEBUG)<<"我是其他内容";return 0;
}                           

执行效果
在这里插入图片描述

2.线程池

2.1线程池介绍

⼀种线程使⽤模式。线程过多会带来调度开销,进⽽影响缓存局部性和整体性能。⽽线程池维护着多 个线程,等待着监督管理者分配可并发执⾏的任务。这避免了在处理短时间任务时创建与销毁线程的 代价。线程池不仅能够保证内核的充分利⽤,还能防⽌过分调度。可⽤线程数量应该取决于可⽤的并发处理器、处理器内核、内存、⽹络sockets等的数量。

2.2线程池的应用场景

  1. 需要⼤量的线程来完成任务,且完成任务的时间比较短。
  2. 对性能要求苛刻的应⽤,⽐如要求服务器迅速响应客⼾请求。
  3. 接受突发性的⼤量请求,但不⾄于使服务器因此产⽣⼤量线程的应⽤。

2.3线程池的设计

创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执⾏任务对象中的任务接⼝。

在这里插入图片描述

2.4代码实现


//条件变量封装
//cond.hpp 
#pragma once#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"namespace CondModule
{using namespace LockModule;class Cond{public:Cond(){int n = pthread_cond_init(&_cond, nullptr); // 初始化条件变量(void)n;}// 等待void Wait(Mutex &mutex) // 让我们的线程释放曾经持有的锁!Mutex -- 封装的锁{int n = pthread_cond_wait(&_cond, mutex.LockPtr()); // 进行等待(void)n;}// 唤醒至少一个线程void Notify(){int n = pthread_cond_signal(&_cond);(void)n;}// 唤醒全部线程void NotifyAll(){int n = pthread_cond_broadcast(&_cond);(void)n;}~Cond(){int n = pthread_cond_destroy(&_cond);(void)n;}private:pthread_cond_t _cond; // 条件变量};
}
//Thread.hpp -- 创建线程封装
#ifndef _THREAD_HPP__
#define _THREAD_HPP__#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>namespace ThreadModule
{//函数包装using func_t = std::function<void(std::string)>;//线程个数static int number = 1;//状态enum class TSTATUS{NEW,RUNNING,STOP};class Thread{private://执行线程函数static void *Routine(void *args){//强制类型转换Thread* thread = (Thread*)args;std::cout<<"我是"<<thread->_name<<"  :";//调用任务thread->_func(thread->_name);return nullptr;}public:Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(false){_name = "Thread-" + std::to_string(number);_pid = getpid();number++;}//创建线程bool Start(){if (_status != TSTATUS::RUNNING) // 保证线程处于非运行状态{int n = pthread_create(&_tid, nullptr, Routine, (void *)this); // 创建线程if (n != 0){return false;}_status = TSTATUS::RUNNING; // 更新状态return true;}return false;}//取消线程bool Stop(){if(_status == TSTATUS::RUNNING) //保证线程处于运行状态{int n = pthread_cancel(_tid);   //取消线程if(n != 0){return false;}_status = TSTATUS::STOP; // 更新状态return true;}return false;}//等待线程bool Join(){//保证线程不处于分离状态且处于运行状态if(!_joinable && _status == TSTATUS::RUNNING)  {   //等待线程,默认不关心线程状态int n = pthread_join(_tid,nullptr);if(n != 0){return false;}_status = TSTATUS::STOP; // 更新状态return true;}return false;}//线程分离void Detach(){//保证线程不处于分离状态且处于运行状态if(!_joinable && _status == TSTATUS::RUNNING)  {int n = pthread_detach(_tid);    //进行线程分离if(n != 0){return ;}std::cout<<"完成线程分离\n"<<std::endl;_joinable = true; //更新分离状态}}bool IsJoinable(){return _joinable;}std::string Name(){return _name;}~Thread() {}private:std::string _name; // 线程namepthread_t _tid;    // 线程idpid_t _pid;        // 进程idbool _joinable;    // 是否是分离的,默认不是func_t _func;      // 线程执行的任务TSTATUS _status;   // 线程状态};
}#endif
/
//ThreadPool.hpp  线程池
namespace ThreadPoolModule
{//展开命名空间using namespace LogMudule;using namespace ThreadModule;using namespace LockModule;using namespace CondModule;//重命名using thread_t = std::shared_ptr<Thread>;//线程池类template <typename T>class ThreadPool{private: const static int defaultnum = 5; //默认的线程数量 //注意:要加static,不然defaultnum需要实例化才生成,这会导致编译时失败//判断任务队列是否为空bool IsEmpty(){return _taskq.empty();}//线程同一执行的函数void HandlerTask(std::string name){//日志LOG(LogLevel::DEBUG)<<name<<"进入主逻辑";//1.拿任务while(true){T t;    //任务LockGuard l(_lock); //访问临界资源 - 加互斥锁//为空且为运行状态时才进行等待while(IsEmpty() && _isrunning){LOG(LogLevel::DEBUG)<<name<<"进入等待";_wait_num++;_cond.Wait(_lock);_wait_num--;LOG(LogLevel::DEBUG)<<name<<"完成等待";} //走到这里只有任务队列不为空或者_isrunning == false -- 这种情况需要结束线程if(IsEmpty() && !_isrunning){break;}//2.取任务t = _taskq.front();_taskq.pop();//3.执行任务t(name);}}public://初始化成员变量ThreadPool(int num = defaultnum) : _num(num), _wait_num(0), _isrunning(false){for(int i = 0; i < _num; i++){//初始化线程池 - 此时还未正式创建线程_threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HandlerTask,this,std::placeholders::_1)));LOG(LogLevel::DEBUG)<<"对象"<<i<<"完成创建";}}//进入任务队列void Equeue(T &&in){//线程池不在运行状态if(_isrunning == false)return;访问临界资源 - 加互斥锁LockGuard l(_lock);_taskq.push(std::move(in));//当有等待线程时需要唤醒if(_wait_num > 0){_cond.Notify();LOG(LogLevel::DEBUG)<<"唤醒一个线程";}}//启动线程池 -- 正式创建线程void Start(){//已经是运行的状态了if(_isrunning)return ;//第一次设置为运行状态_isrunning = true;for(auto &e : _threads){e->Start();}}//进程线程等待void Wait(){for(auto &e : _threads){e->Join();} }//暂停线程池void Stop(){访问临界资源 - 加互斥锁LockGuard l(_lock);if(_isrunning == false)return;//修改状态  -- 此时不能添加任务了_isrunning = false;//唤醒其他线程,再让其他线程将剩下的任务执行完了,最后再自己退出。if(_wait_num > 0){_cond.NotifyAll();}}~ThreadPool(){}private:std::vector<thread_t> _threads; //线程组sint _num;   //线程数量int _wait_num;  //正在等待的线程数量std::queue<T> _taskq; // 临界资源Mutex _lock;    //锁Cond _cond;     //条件变量bool _isrunning;    //当前线程状态};}
//
//Main.cc
#include"ThreadPool.hpp"using namespace ThreadPoolModule;void add(std::string name)
{LOG(LogLevel::DEBUG)<<name<<"进入计算";
}int main()
{ThreadPool<func_t> tp;//启动tp.Start();int n = 10;while(n--){//入任务tp.Equeue(add);sleep(1);}//暂停tp.Stop();//等待tp.Wait();return 0;
}

执行效果
在这里插入图片描述

2.5修改为单例模式

2.5.1什么是单例模式

该类的实例化对象在整个代码中只能有一个。

2.5.2实现单例模式的方式

  1. 饿汉模式:不管什么,一上来先创建好。
  2. 懒汉模式:需要用时才进行创建。

2.5.3使用懒汉模式实现线程池单例

//1。将构造函数私有化和删除拷贝构造和赋值重载 -这样外部就无法创建了
private:ThreadPool(int num = defaultnum) : _num(num), _wait_num(0), _isrunning(false){for(int i = 0; i < _num; i++){_threads.push_back(std::make_shared<Thread>		(std::bind(&ThreadPool::HandlerTask,this,std::placeholders::_1)));LOG(LogLevel::DEBUG)<<"对象"<<i<<"完成创建";}}ThreadPool(const ThreadPool<T> & tp) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;//2.提供一个静态线程池对象指针和一个互斥锁static ThreadPool<T> *tp;   //线程池指针static Mutex _mutex;    //为单例的锁public://3.提供静态函数-全局可用,需要用线程池时就调用获取线程池对象static ThreadPool<T> *getInstance(){//双if判断可以对减少对锁的申请if(tp == nullptr){//因为被多线程调用时,当多个线程同时进入时可能会被创建多个,所以加互斥锁LockGuard l(_mutex);if(tp == nullptr){	//创建线程池tp = new ThreadPool<T>();LOG(LogLevel::DEBUG)<<"第一次完成创建";}}return tp;}
/// 
//调用方式
//main.cc
void add(std::string name)
{LOG(LogLevel::DEBUG)<<name<<"进入计算";
}
int main()
{ThreadPool<func_t>::getInstance()->Start();int n = 10;while(n--){ThreadPool<func_t>::getInstance()->Equeue(add);sleep(1);}ThreadPool<func_t>::getInstance()->Stop();ThreadPool<func_t>::getInstance()->Wait();return 0;
}

3.线程安全和函数重入问题

3.1线程安全和函数重入的概念

  1. 线程安全就是多个线程在访问共享资源时,能够正确地执⾏,不会相互⼲扰或破坏彼此的执行结果。⼀般⽽⾔,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进⾏操作,并且没有锁保护的情况下,容易出现该问题。
  2. 重⼊:同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊,我们称之为重⼊。⼀个函数在重⼊的情况下,运⾏结果不会出现任何不同或者任何问题,则该函数被 称为可重⼊函数,否则,是不可重⼊函数。
    一般的可重入:
    多线程访问函数
    信号导致一个执行流多次进入同一个函数

3.2总结

可重入函数一定是线程安全的,线程安全的函数不一定是可重入的(如一个执行流正在执行一个加了锁的线程安全函数并且此时拿着锁,但是由于信号(该信号的处理函数也是当前函数)导致该线程中断并去执行信号处理函数,当该执行流申请锁时被锁阻塞,而该执行流又无法回去释放锁,这使当前线程一直被阻塞,从而出现死锁)。
补充:信号是由进程其中的一个线程处理的,每个线程中都有信号屏蔽字,信号屏蔽字可以设置屏蔽信号。

4.死锁

4.1什么是死锁

死锁是指在⼀组进程或者线程中的各个进程或者线程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的⼀种永久等待状态。如:假设临界资源同时需要两把锁才能访问,线程A申请了其中一把锁,线程B申请了另一把锁,此时线程A和B都会去申请没拿到的锁,但是线程A和B都不释放自己手里的锁,这导致线程A和B都被一直阻塞住了。

4.2产生死锁的必要条件

  1. 互斥条件:⼀个资源每次只能被⼀个执行流使用。
  2. 请求与保持条件:⼀个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:⼀个执行流已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若⼲执行流之间形成⼀种头尾相接的循环等待资源的关系。

4.3避免死锁

破坏其中的一个条件即可。
如:破坏循环等待条件问题:资源⼀次性分配, 使⽤超时机制、加锁顺序⼀致

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

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

相关文章

AI时代的PPT革命:智能生成PPT工具为何备受青睐?

在日常工作和学习中&#xff0c;PPT是我们不可或缺的表达工具。制作一份精美的PPT常常需要耗费数小时&#xff0c;甚至几天的时间。从选择主题到调整排版&#xff0c;琐碎的细节让人筋疲力尽。但现在一种名为“AI生成PPT”的技术正悄然崛起&#xff0c;彻底颠覆了传统PPT制作的…

结构方程模型(SEM)入门到精通:lavaan VS piecewiseSEM、全局估计/局域估计;潜变量分析、复合变量分析、贝叶斯SEM在生态学领域应用

目录 第一章 夯实基础 R/Rstudio简介及入门 第二章 结构方程模型&#xff08;SEM&#xff09;介绍 第三章 R语言SEM分析入门&#xff1a;lavaan VS piecewiseSEM 第四章 SEM全局估计&#xff08;lavaan&#xff09;在生态学领域高阶应用 第五章 SEM潜变量分析在生态学领域…

CANopen多电机控制的性能分析

在 CANopen 总线上控制多台电机并实时获取位置和速度信息&#xff0c;通信速度受到总线带宽、电机数量、数据刷新频率等因素影响。在 LabVIEW 开发中&#xff0c;利用 PDO 优化数据传输&#xff0c;合理设置刷新周期&#xff0c;并结合高效任务管理&#xff0c;可以显著提高多电…

图论入门编程

卡码网刷题链接&#xff1a;98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …

政安晨【零基础玩转各类开源AI项目】探索Cursor-AI Coder的应用实例

目录 Cusor的主要特点 Cusor实操 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; Cursor 是 Visual Studio Code 的一个分支。这使我们能够…

Taro 鸿蒙技术内幕系列(三) - 多语言场景下的通用事件系统设计

基于 Taro 打造的京东鸿蒙 APP 已跟随鸿蒙 Next 系统公测&#xff0c;本系列文章将深入解析 Taro 如何实现使用 React 开发高性能鸿蒙应用的技术内幕 背景 在鸿蒙生态系统中&#xff0c;虽然原生应用通常基于 ArkTS 实现&#xff0c;但在实际研发过程中发现&#xff0c;使用 C…

二,[ACTF2020 新生赛]Include1感谢 Y1ng 师傅供题。

进入靶场后&#xff0c;发现tips可以点击 点击后进入此页面 猜测此为文件上传漏洞,构造payload&#xff0c;并成功得到base64编码后的源码 详解payload&#xff1a; php://filter/readconvert.base64-encode/resourceflag.php 1.php://filter是PHP中的一个流封装协议&#xf…

EXTI配置流程 含中断延时消抖点亮小灯

如图可知&#xff0c;配置流程分成以下一个部分 ①使能GPIO时钟 __HAL_RCC_GPIOA_CLK_ENABLE();// 打开时钟 ②初始化利用 HAL_GPIO_Init 一步到位&#xff0c;可以初始化外设GPIO的一切 4个参数 &#xff08;引脚 Pull 这里选择的模式是从下面这几个里面选 速度&#x…

AIGC-----AIGC在虚拟现实中的应用前景

AIGC在虚拟现实中的应用前景 引言 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术的应用也迎来了新的契机。AIGC与VR的结合为创造沉浸式体验带来了全新的可能性&#xff0c;这种组合不仅极大地降低了VR内容的…

Docker部署mysql:8.0.31+dbsyncer

Docker部署mysql8.0.31 创建本地mysql配置文件 mkdir -p /opt/mysql/log mkdir -p /opt/mysql/data mkdir -p /opt/mysql/conf cd /opt/mysql/conf touch my.config [mysql] #设置mysql客户端默认字符集 default-character-setUTF8MB4 [mysqld] #设置3306端口 port33…

大语言模型LLM的微调代码详解

代码的摘要说明 一、整体功能概述 这段 Python 代码主要实现了基于 Hugging Face Transformers 库对预训练语言模型&#xff08;具体为 TAIDE-LX-7B-Chat 模型&#xff09;进行微调&#xff08;Fine-tuning&#xff09;的功能&#xff0c;使其能更好地应用于生成唐诗相关内容的…

解决SSL VPN客户端一直提示无法连接服务器的问题

近期服务器更新VPN后&#xff0c;我的win10电脑一致无法连接到VPN服务器&#xff0c; SSL VPN客户端总是提示无法连接到服务端。网上百度尝试了各种方法后&#xff0c;终于通过以下设置方式解决了问题&#xff1a; 1、首先&#xff0c;在控制面板中打开“网络和共享中心”窗口&…

《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试

一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器&#xff0c;旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…

基础入门-Web应用架构搭建域名源码站库分离MVC模型解析受限对应路径

知识点&#xff1a; 1、基础入门-Web应用-域名上的技术要点 2、基础入门-Web应用-源码上的技术要点 3、基础入门-Web应用-数据上的技术要点 4、基础入门-Web应用-解析上的技术要点 5、基础入门-Web应用-平台上的技术要点 一、演示案例-域名差异-主站&分站&端口站&…

Qt中2D绘制系统

目录 一、Qt绘制系统 1.1Qt绘制基本概念 1.2 绘制代码举例 1.3画家 1.3.1 QPainter的工作原理&#xff1a; 1.3.2 自定义绘制饼状图&#xff1a; 1.4画笔和画刷 1.4.1画笔 1.4.2 画刷填充样式 1.5 反走样和渐变 1.6绘制设备 1.7坐标变换 1.8QPainterPath 1.9绘制文…

Servlet细节

目录 1 Servlet 是否符合线程安全&#xff1f; 2 Servlet对象的创建时间&#xff1f; 3 Servlet 绑定url 的写法 3.1 一个Servlet 可以绑定多个url 3.2 在web.xml 配置文件中 url-pattern写法 1 Servlet 是否符合线程安全&#xff1f; 答案&#xff1a;不安全 判断一个线程…

VisionPro 机器视觉案例 之 凹点检测

第十六篇 机器视觉案例 之 凹点检测 文章目录 第十六篇 机器视觉案例 之 凹点检测1.案例要求2.实现思路2.1 方式一&#xff1a;斑点工具加画线工具加点线距离工具2.2 方法二 使用斑点工具的结果集边缘坐标的横坐标最大值ImageBoundMaxX2.3 方法三 使用斑点工具的结果集凹点结果…

SAP 零售方案 CAR 系统的介绍与研究

前言 当今时代&#xff0c;零售业务是充满活力和活力的业务领域之一。每天&#xff0c;由于销售运营和客户行为&#xff0c;它都会生成大量数据。因此&#xff0c;公司迫切需要管理数据并从中检索见解。它将帮助公司朝着正确的方向发展他们的业务。 这就是为什么公司用来处理…

手搓人工智能—聚类分析(下)谱系聚类与K-mean聚类

“无论结果如何&#xff0c;至少我们存在过” ——《无人深空》 前言 除了上一篇手搓人工智能-聚类分析&#xff08;上&#xff09;中提到的两种简单聚类方式&#xff0c;还有一些更为常用、更复杂的聚类方式&#xff1a;谱系聚类&#xff0c;K-均值聚类。 谱系聚类 谱系聚类…

【c语言】文件操作详解 - 从打开到关闭

文章目录 1. 为什么使用文件&#xff1f;2. 什么是文件&#xff1f;3. 如何标识文件&#xff1f;4. 二进制文件和文本文件&#xff1f;5. 文件的打开和关闭5.1 流和标准流5.1.1 流5.1.2 标准流 5.2 文件指针5.3 文件的打开和关闭 6. 文件的读写顺序6.1 顺序读写函数6.2 对比一组…