目录一、准备工作线程封装锁封装条件变量封装二、日志2.1、策略模式2.2、日志设计2.3、日志实现2.3.1 策略模式实现日志显示2.3.2 日志等级处理2.3.3 时间戳处理2.3.4 形成一条日志2.3.5 完整代码及效果演示三、线程池3.1、线程池设计完整代码及效果演示3.2、线程安全的单例模式3.2.1 饿汉模式3.2.2 懒汉模式3.3、完整代码及效果演示一、准备工作线程封装锁封装条件变量封装二、日志2.1、策略模式设计模式是前人总结的最佳实践模板解决面向对象设计中反复出现的特定问题让代码更灵活、可维护、可复用。共有 23 种经典设计模式策略模式就属于其中一种。策略模式即定义一系列算法把它们一个个封装起来并且使它们可以互相替换。C中可以利用多态实现策略模式// 策略接口基类classPaymentStrategy{public:virtualvoidpay(intamount)0;// 纯虚函数virtual~PaymentStrategy()default;};// 具体策略支付宝classAlipay:publicPaymentStrategy{public:virtualvoidpay(intamount)override{cout支付宝支付 amount 元endl;}};// 具体策略微信支付classWeChatPay:publicPaymentStrategy{public:virtualvoidpay(intamount)override{cout微信支付 amount 元endl;}};// 上下文购物车classShoppingCart{PaymentStrategy*strategy;// 持有策略基类指针public:voidsetStrategy(PaymentStrategy*s){strategys;}voidcheckout(intamount){strategy-pay(amount);// 调用当前策略多态}};// 使用intmain(){ShoppingCart cart;cart.setStrategy(newAlipay());cart.checkout(100);// 支付宝支付 100 元cart.setStrategy(newWeChatPay());cart.checkout(200);// 微信支付 200 元}未来我们的日志无非就是打印到显示器上或者写入文件。所以就可以实现向显示器打印和向文件写入两个子类。2.2、日志设计计算机中的日志是记录系统和软件运行中发生事件的文件主要作用是监控行状态、记录异常信息帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。日志应当有以下几部分组成时间戳日志等级日志内容以下几个部分可选进程线程id文件名行号日志等级等级含义DEBUG调试信息INFO正常运行信息WARN警告非致命ERROR错误功能受损FATAL致命系统崩溃我们打算设计的日志格式如下[可读性很好的时间] [日志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容支持可变参数 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world2.3、日志实现2.3.1 策略模式实现日志显示策略接口基类classLogStrategy{public:virtualvoidSyncLog(conststd::string message)0;// 纯虚函数~LogStrategy()default;};具体策略向显示器打印注意显示器属于临界资源为了避免打印出现数据错乱需要加锁。classConsoleLogStrategy:publicLogStrategy{public:virtualvoidSyncLog(conststd::string message)override{Mutex_RAIIguard(_mutex);// 加锁std::coutmessage\r\n;}~ConsoleLogStrategy(){}private:Mutex _mutex;};具体策略向文件打印细节一我们需要一个已经存在文件路径自己起一个文件名。那么就需要我们检查文件路径可以用std::filesystem中的exists方法检查std::filesystem是C17标准库统一了跨平台的文件系统操作。细节二我们向文件写入日志应该以追加方式写入。可以用std::ofstream方法以追加方式打开一个文件不存在则创建。细节三std::ofstream打开文件我们可以以追加重定向的方式向文件写入日志。细节四文件同样是共享资源凡是涉及访问共享资源可能存在问题我们必须加锁。std::string gdefaultPath./;// 默认当前路径std::string gdefaultNamelog.log;// 默认文件名classFileLogStrategy:publicLogStrategy{public:FileLogStrategy(std::string pathgdefaultPath,std::string namegdefaultName):_path(path),_name(name){// 对路径做检查Mutex_RAIIguard(_mutex);// 加锁if(std::filesystem::exists(_path)){return;}try{std::filesystem::create_directories(_path);}catch(conststd::filesystem::filesystem_errorerr){std::couterr.what()std::endl;}}virtualvoidSyncLog(conststd::string message)override{Mutex_RAIIguard(_mutex);// 加锁// 构造文件完整路径std::string filename_path(_path.back()/?:/)_name;// 以追加方式打开文件不存在创建std::ofstreamout(filename,std::ios::app);if(!out){return;}outmessage\r\n;out.close();}~FileLogStrategy(){}private:std::string _path;// 文件路径std::string _name;// 文件名Mutex _mutex;// 锁};2.3.2 日志等级处理日志等级我们用应该联合体表示在联合体中日志等级其实就是一个整数而我们在拼接日志时希望以字符串形式显示日志等级所以我们需要做一下转换。// 形成日志等级enumclassLogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 将日志等级转化为字符串形式LogLevel对象实际是一个整数std::stringLevel2Str(LogLevel level){switch(level){caseLogLevel::DEBUG:returnDEBUG;caseLogLevel::INFO:returnINFO;caseLogLevel::WARNING:returnWARNING;caseLogLevel::ERROR:returnERROR;caseLogLevel::FATAL:returnFATAL;default:returnUNKOWN;}}2.3.3 时间戳处理我们想要一个可读性很好的日志如2024-08-04 12:27:03可以用snprintf进行格式控制。获取当前时间戳timelocaltime_r可以将时间戳转化为一个结构体。通过这个结构体就可以获得年月日等等。// 获取时间std::stringTime(){time_t nowtime(nullptr);// 获取当前时间戳tm tm;localtime_r(now,tm);charbuf[128];// 格式控制snprintf(buf,sizeof(buf),%4d-%02d-%02d %02d:%02d:%02d,tm.tm_year1900,tm.tm_mon1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);returnbuf;}2.3.4 形成一条日志细节一我们采用在Logger类中在实现一个LogMessage类。Logger类决定最终调用哪个策略向显示器打印或向文件打印。LogMessage类负责按照格式将所有数据形成日志。为什么要在类中实现类这样LogMessage类形成日志之后就可以调用Logger的方法显示日志。在LogMessage对象析构时我们将日志打印出来由指针的类型最终决定调用哪个方法多态。细节二Logger类中可以用智能指针基类函数指针类型来帮我们调用策略函数同时主动内存管理。细节三形成一条日志可以用std::stringstream实现字符串和数据的流式转换用重定向的方式将任意类型全部转化为字符串非常方便。细节四未来我们的日志输出形式Logger() xxx xxx。所以就需要重载操作符。同时可能连续向显示器或文件打印 所以为了支持这种写法可以返回this指针指向的对象。其次类型不同我们可以先将其全部转化为字符串类型。细节五未来日志等级打印日志的文件名行号需要我们自己决定。但在C/C中有两个预定义宏__FILE__和__LINE__由编译器在预处理阶段自动替换为当前文件名和行号。所以我们可以再处理一下将logger(level, filename, line)定义为一个宏Log(levle)未来直接Log(level) xxx xxx调用即可。如何实现由于LogMessage类在Logger中必须在Logger类中实例化LogMessage类对象就需要重载()当我们调用Log(levle)编译时替换为logger(level, filename, line)在重载函数内部LogMessage(level, file, line, *this)实例化LogMessage 对象。classLogger{public:Logger(){Enable_ConsoleLogStrategy();}// 创建实例化智能指针对象voidEnable_ConsoleLogStrategy(){_fllush_strategystd::make_uniqueConsoleLogStrategy();}voidEnable_FileLogStrategy(){_fllush_strategystd::make_uniqueFileLogStrategy();}// 形成一条日志// 一条日志的形式[时间] [日志等级] [pid] [文件名] [行数] - 内容// [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello worldclassLogMessage{public:LogMessage(LogLevellevel,conststd::stringfile,size_t line,Loggerlogger):_cur_time(Time()),_loglevel(Level2Str(level)),_pid(getpid()),_file(file),_line_num(line),_logger(logger){// 日志左半部分std::stringstream ss;ss[_cur_time] [_loglevel] [_pid] [_file] [_line_num] - ;_logss.str();}// 日志输出形式: Logger() xxx xxx;// 重载 templatetypenameTLogMessageoperator(constTinfo){// 字符串流std::stringstream ss;ssinfo;_logss.str();return*this;// 返回LogMessage对象处理连续的如 xxx 123 a}// 析构~LogMessage(){// 调用日志显示方法多态if(_logger._fllush_strategy){// 由该智能指针类型决定调用哪个方法_logger._fllush_strategy-SyncLog(_log);}}private:std::string _cur_time;// 时间std::string _loglevel;// 日志等级pid_t _pid;// 进程idstd::string _file;// 文件名size_t _line_num;// 行号std::string _log;// 整条日志Logger_logger;// 外部类对象内部类用来调用日志显示方法};// 重载()在内部类实例化Logger对象LogMessageoperator()(LogLevel level,conststd::string file,size_t line){returnLogMessage(level,file,line,*this);}~Logger(){}private:std::unique_ptrLogStrategy_fllush_strategy;// 日志显示方法智能指针};Logger logger;#defineLog(level)logger(level,__FILE__,__LINE__)2.3.5 完整代码及效果演示代码在这里日志测试main.cc#includeLog.hppusingnamespaceLogModuel;intmain(){logger.Enable_ConsoleLogStrategy();// 向显示器打印logger(LogLevel::DEBUG,main.cc,9)hello123;logger(LogLevel::DEBUG,main.cc,9)xxxxx123;logger(LogLevel::DEBUG,main.cc,9)aaaaa123;logger.Enable_FileLogStrategy();// 向文件打印logger(LogLevel::INFO,main.cc,9)hello123;logger(LogLevel::INFO,main.cc,9)xxxxx123;logger(LogLevel::INFO,main.cc,9)aaaaa123;return0;}三、线程池3.1、线程池设计线程池一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。线程池的应用场景需要大量的线程来完成任务且完成任务的时间比较短。对性能要求苛刻的应用比如要求服务器迅速响应客户请求。接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。即我们先创建一批线程用特定的数据结构维护起来。没有任务时线程在对应的条件变量下阻塞等待有任务时唤醒线程来处理。流程启动线程池创建线程并维护向线程池排放任务唤醒线程处理任务关闭线程池回收线程完整代码及效果演示线程池完整代码在这里任务代码在这里线程池测试main.cc#includeLog.hpp#includePthreadPool.hpp#includeTask.hppusingnamespacePthreadPoolMoudel;usingnamespaceLogModuel;intmain(){Logger logger;logger.Enable_ConsoleLogStrategy();// 日志打印到显示器PthreadPooltask_t*poolnewPthreadPooltask_t();pool-Start();// 启动线程池sleep(3);intcnt10;while(cnt){pool-Equeue(Download);sleep(1);cnt--;}pool-Stop();// 关闭线程池pool-Join();// 回收线程return0;}3.2、线程安全的单例模式单例即只有一个实例化的对象。在很多服务器开发场景中经常需要让服务器加载很多的数据 (上百G) 到内存中为了节省空间此时往往要用一个单例的类来管理这些数据。单例模式有两种经典的实现模式饿汉模式与懒汉模式。举个例子洗碗吃完饭立刻洗碗这种就是饿汉方式。因为下一顿吃的时候可以立刻拿着碗就能吃饭。吃完饭先把碗放下然后下一顿饭用到这个碗了再洗碗就是懒汉方式。懒汉方式最核心的思想是 “延时加载”。从而能够优化服务器的启动速度这不难理解。在操作系统的设计哲学中绝大多数场景都采用这种方法比如我们申请空间实例化对象等等。3.2.1 饿汉模式classEagerSingleton{public:staticEagerSingletongetInstance(){return_instance;}private:EagerSingleton()default;// 构造函数什么都不干// 禁止拷贝EagerSingleton(constEagerSingleton)delete;EagerSingletonoperator(constEagerSingleton)delete;staticEagerSingleton _instance;// 程序启动立即实例化对象};3.2.2 懒汉模式classLazySingleton{public:staticLazySingleton*GetInstance(){// 需要的时候创建if(instNULL){instnewLazySingleton();}returninst;}private:LazySingleton()default;// 构造函数什么都不干// 禁止拷贝LazySingleton(constLazySingleton)delete;LazySingletonoperator(constLazySingleton)delete;staticLazySingleton*inst;};3.3、完整代码及效果演示我们以懒汉模式实现代码在这里测试main.cc#includeLog.hpp#includePthreadPool.hpp#includeTask.hppusingnamespacePthreadPoolMoudel;usingnamespaceLogModuel;intmain(){Logger logger;logger.Enable_ConsoleLogStrategy();// 日志打印到显示器sleep(3);intcnt10;while(cnt){// 使用时创建单例但只有第一次才创建对象PthreadPooltask_t::GetInstance()-Equeue(Download);sleep(1);cnt--;}PthreadPooltask_t::GetInstance()-Stop();// 关闭线程池PthreadPooltask_t::GetInstance()-Join();// 回收线程return0;}线程池项目完整代码在这里日志 单例模式设计线程池今天的分享到此结束如果感觉还不错点个赞支持一下吧下次再见