Linux中线程池的简单实现 -- 线程安全的日志模块,策略模式,线程池的封装设计,单例模式,饿汉式单例模式,懒汉式单例模式

目录

1. 对线程池的理解

1.1 基本概念

1.2 工作原理

1.3 线程池的优点

2. 日志与策略模式

2.1 日志认识

2.2 策略模式

2.2.1 策略模式的概念

2.2.2 工作原理

2.2 自定义日志系统的实现

3. 线程池设计

3.1 简单线程池的设计

3.2 线程安全的单例模式线程池的设计

3.2.1 单例模式

3.2.1.1 概念

3.2.1.2 设计原理

3.2.2 饿汉实现方式

3.2.3 懒汉实现方式


1. 对线程池的理解

        线程池是一种多线程处理形式,用于管理和复用线程资源,以提高程序的性能和效率。

1.1 基本概念

        线程池是一种线程的使用模式,它预先创建一定数量的线程,并将这些线程存储在一个“池”(容器)中,当有新的任务提交时,线程池会从池中取出一个空闲的线程来执行该任务;如果池中没有空闲线程且线程数量未达到最大限制,会创建新的线程来处理任务;若线程数量已达到最大限制,任务会被放入任务队列中等待。任务执行完毕后,线程不会被销毁,而是返回到线程池中,等待下一个任务。

1.2 工作原理

        (1)线程池的初始化:在创建线程池时,会根据配置参数创建一定数量的线程,并将它们置于就绪状态,等待任务的到来。

        (2)任务提交:当有新的任务需要执行时,将任务添加到任务队列中。

        (3)线程分配:线程池中的线程会不断地从任务队列中获取任务。如果队列中有任务,线程会取出任务并执行;如果队列为空,线程会进入等待状态,直到有新的任务加入。

        (4)任务执行:线程获取到任务后,会执行任务中的逻辑。任务执行完成后,线程会释放资源,并再次回到线程池中等待下一个任务。

        (5)线程池的关闭:当线程池不再需要使用时,可以关闭线程池。关闭过程中,会停止接受新的任务,并等待已有的任务执行完毕,最后销毁所有线程。

1.3 线程池的优点

        (1)提高性能:避免了频繁创建和销毁线程带来的开销。线程的创建和销毁是比较昂贵的操作,使用线程池可以复用已有的线程,减少了系统资源的消耗,提高了程序的响应速度。

        (2)资源管理:可以对线程的数量进行有效控制,避免因创建过多线程导致系统资源耗尽。通过设置线程池的最大线程数,可以确保系统在高并发情况下仍能稳定运行。

        (3)提高响应速度:由于线程池中的线程已经预先创建好,当有任务提交时,可以立即分配线程执行任务,减少了任务的等待时间。

2. 日志与策略模式

2.1 日志认识

        计算机中的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态,记录异常信息,帮助快速定位问题并支持程序员进行问题修复。是系统维护,故障排除和安全管理的重要工具。

        日志有现有的解决方案,如:spdlog,glog,Boost.Log,Log4cxx等等。

        下面实现一个自定义类型的日志系统,日志的格式有以下指标:时间,日志等级,进程pid,输出日志的文件,行号,日志内容。

        下列是期望输出的日志格式:

2.2 策略模式

2.2.1 策略模式的概念

        策略模式定义了一系列算法,将每个算法都封装起来,并且使它们可以互相替换。该模式让算法的变化独立于使用算法的客户,属于行为型模式。通过使用策略模式,可以将算法的选择和实现分离,使得代码更加灵活、可维护和可扩展。

2.2.2 工作原理

        (1) 策略接口或抽象类:定义了一个公共的接口或抽象类,用于规范具体策略类的行为。这个接口或抽象类中声明了一个或多个抽象方法,这些方法代表了不同策略可以执行的操作。

        (2) 具体策略类:实现了策略接口或继承自抽象策略类,每个具体策略类都实现了特定的算法或行为。它们是策略模式的核心,负责具体的业务逻辑实现。

        (3) 上下文类:持有一个策略接口的引用,通过该引用调用具体策略类的方法来执行相应的算法。上下文类可以根据不同的条件或用户需求,动态地切换使用不同的具体策略类。本质上就是多态特性。

2.2 自定义日志系统的实现

        由于实现的日志系统要支持多线程程序日志的有序打印,所以不管在访问显示器还是访问文件的时候都需要通过加锁来维护线程之间的互斥关系。

        这里对 Linux 系统中的 pthread 库中的互斥锁进行面向对象的封装:

// Mutex.hpp#pragma once 
#include <pthread.h>// 将互斥量接口封装成面向对象的形式
namespace MutexModule
{class Mutex{public:Mutex(){int n = pthread_mutex_init(&_mutex, nullptr);(void)n;}~Mutex(){int n = pthread_mutex_destroy(&_mutex);(void)n;}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}pthread_mutex_t* Get()  //  获取原生互斥量的指针{return &_mutex;}private:pthread_mutex_t _mutex;};// 采用RAII风格进行锁管理,当局部临界区代码运行完的时候,局部LockGuard类型的对象自动进行释放,调用析构函数释放锁class LockGuard{public:LockGuard(Mutex &mutex): _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex& _mutex;};
}

        实现的一些细节在下列代码中的注释中有所体现。 

// Log.hpp#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <ctime>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;const std::string gsep = "\r\n";// 策略模式 -- 利用C++的多态特性// 1. 刷新策略 a: 向显示器打印 b: 向文件中写入// 刷新策略基类class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 显示器打印日志的策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}void SyncLog(const std::string &message) override{// 加锁使多线程原子性的访问显示器LockGuard lockGuard(_mutex);std::cout << message << gsep;}~ConsoleLogStrategy(){}private:Mutex _mutex;};// 文件打印日志策略// 默认的日志文件路径和日志文件名const std::string defaultPath = "./log";const std::string defaultFile = "my.log";class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultPath, const std::string &file = defaultFile): _path(path),_file(file){// 加锁使多线程原子性的访问文件LockGuard lockGuard(_mutex);// 判断目录是否存在if (std::filesystem::exists(_path)) // 检测文件系统对象(文件,目录,符号链接等)是否存在{return;}try{// 如果目录不存在,递归创建目录std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e) // 如果创建失败则打印异常信息{std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockGuard(_mutex);// 追加方式向文件中写入std::string fileName = _path + (_path.back() == '/' ? "" : "/") + _file;// std::ofstream是C++标准库中用于输出到文件的流类,主要用于将数据写入文件std::ofstream out(fileName, std::ios::app);if (!out.is_open()){return;}out << message << gsep;out.close();}~FileLogStrategy(){}private:std::string _path; // 日志文件所在路径std::string _file; // 日志文件本身Mutex _mutex;};// 2. 形成完整日志并刷新到指定位置// 2.1 日志等级enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 2.2 枚举类型的日志等级转换为字符串类型std::string Level2Str(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "UNKNOWN";}}// 2.3 获取当前时间的函数std::string GetCurTime(){// time 函数参数为一个time_t类型的指针,若该指针不为NULL,会把获取到的当前时间值存储在指针指向的对象中// 若传入为NULL,则仅返回当前时间,返回从1970年1月1日0点到目前的秒数time_t cur = time(nullptr);struct tm curTm;// localtime_r是localtime的可重入版本,主要用于将time_t类型表示的时间转换为本地时间,存储在struct tm 结构体中localtime_r(&cur, &curTm);char timeBuffer[128];snprintf(timeBuffer, sizeof(timeBuffer), "%4d-%02d-%02d %02d:%02d:%02d",curTm.tm_year + 1900,curTm.tm_mon + 1,curTm.tm_mday,curTm.tm_hour,curTm.tm_min,curTm.tm_sec);return timeBuffer;}// 2.4 日志形成并刷新class Logger{public:// 默认刷新到显示器上Logger(){EnableConsoleLogStrategy();}void EnableConsoleLogStrategy(){// std::make_unique用于创建并返回一个std::unique_ptr对象_fflushStrategy = std::make_unique<ConsoleLogStrategy>();}void EnableFileLogStrategy(){_fflushStrategy = std::make_unique<FileLogStrategy>();}//  内部类默认是外部类的友元类,可以访问外部类的私有成员变量//  内部类LogMessage,表示一条日志信息的类class LogMessage{public:LogMessage(LogLevel &level, std::string &srcName, int lineNum, Logger &logger): _curTime(GetCurTime()),_level(level),_pid(getpid()),_srcName(srcName),_lineNum(lineNum),_logger(logger){// 日志的基本信息合并起来// std::stringstream用于在内存中进行字符串的输入输出操作, 提供一种方便的方式处理字符串// 将不同类型的数据转换为字符串,也可以将字符串解析为不同类型的数据std::stringstream ss;ss << "[" << _curTime << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _srcName << "] "<< "[" << _lineNum << "] "<< "- ";_logInfo = ss.str();}//  使用模板重载运算符<< -- 支持不同数据类型的输出运算符重载template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_logInfo += ss.str();return *this;}~LogMessage(){if (_logger._fflushStrategy){_logger._fflushStrategy->SyncLog(_logInfo);}}private:std::string _curTime;   // 日志时间LogLevel _level;    // 日志等级pid_t _pid; // 进程pidstd::string _srcName;   // 输出日志的文件名int _lineNum;   //输出日志的行号std::string _logInfo;   //完整日志内容Logger &_logger;    // 方便使用策略进行刷新};// 使用宏进行替换之后调用的形式如下// logger(level, __FILE__, __LINE__) << "hello world" << 3.14;// 这里使用仿函数的形式,调用LogMessage的构造函数,构造一个匿名的LogMessage对象// 返回的LogMessage对象是一个临时对象,它的生命周期从创建开始到包含它的完整表达式结束(可以简单理解为包含// 这个对象的该行代码)// 代码调用结束的时候,如果没有LogMessage对象进行临时对象的接收,则会调用析构函数,// 如果有LogMessage对象进行临时对象的接收,会调用拷贝构造或者移动构造构造一个对象,并析构临时对象// 所以通过临时变量调用析构函数进行日志的打印LogMessage operator()(LogLevel level, std::string name, int line){return LogMessage(level, name, line, *this);}~Logger(){}private:std::unique_ptr<LogStrategy> _fflushStrategy;};//  定义一个全局的Logger对象Logger logger;// 使用宏定义,简化用户操作并且获取文件名和行号#define LOG(level) logger(level, __FILE__, __LINE__) // 使用仿函数的方式进行调用#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}#endif

        通过下列方式进行调用 

#include "Log.hpp"using namespace LogModule;int main()
{Enable_Console_Log_Strategy();LOG(LogLevel::DEBUG) << "debug";LOG(LogLevel::INFO) << "info";LOG(LogLevel::WARNING) << "warning";LOG(LogLevel::ERROR) << "error";LOG(LogLevel::FATAL) << "fatal";Enable_File_Log_Strategy();LOG(LogLevel::DEBUG) << "hello world";LOG(LogLevel::DEBUG) << "hello world";LOG(LogLevel::DEBUG) << "hello world";return 0;
}

        向显示器上打印的部分: 

        向 my.log 文件中打印的部分 

3. 线程池设计

3.1 简单线程池的设计

        这里模拟创建一个固定线程数量的线程池,循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中的任务。下列线程,日志,条件变量,互斥量等的封装参考前面的博客。

// ThreadPool.hpp#pragma once#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;static const int gnum = 5;  // 使用全局变量来表示一个线程池默认的线程数量template <typename T>   // 使用模版的方式使线程池支持多类型的任务class ThreadPool{private:void WakeUpAllThread(){if (_sleep_num)_cond.Broadcast();LOG(LogLevel::DEBUG) << "唤醒所有休眠线程";}void WakeOne(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠的线程";}public:// 创建固定数量的线程ThreadPool(int num = gnum): _num(num),_isrunning(false),_sleep_num(0){for (int i = 0; i < _num; i++){_threads.emplace_back([this](){ HandlerTask(); }); // 调用线程的构造函数,线程的构造函数形参是一个回调函数}}void HandlerTask(){char name[64];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;// 处理任务{LockGuard lockGuard(_mutex);// 1. 队列为空,线程池没有退出,进行休眠while (_taskq.empty() && _isrunning){_sleep_num++;LOG(LogLevel::INFO) << name << " 进入休眠";_cond.Wait(_mutex);_sleep_num--;}// 2. 任务为空,线程池退出,则该线程退出if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出, 因为线程池退出&&任务队列为空";break;}// 3. 获取任务t = _taskq.front();_taskq.pop();}t(); // 4. 处理任务// LOG(LogLevel::DEBUG) << name << " is running";}}bool Enqueue(const T &in){if (_isrunning) // 如果线程池停止,则停止入任务{LockGuard lockGuard(_mutex);_taskq.push(in);if (_threads.size() == _sleep_num)  // 如果全部线程都在休眠,则唤醒一个线程WakeOne();return true;}return false;}void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();}}void Stop(){// 1. 将运行标志位置为falseLockGuard lockGuard(_mutex);if (!_isrunning)return;_isrunning = false;// 2. 唤醒休眠的线程,然后再HandlerTask中进行退出WakeUpAllThread();}void Join(){for (auto &thread : _threads){thread.Join();LOG(LogLevel::INFO) << thread.GetName() << " 被Join";}}~ThreadPool(){}private:std::vector<Thread> _threads;int _num; // 线程数量std::queue<T> _taskq;   // 任务队列Cond _cond;Mutex _mutex;bool _isrunning;int _sleep_num;};
}
//Task.hpp#pragma once#include <iostream>
#include <functional>
#include <unistd.h>
#include "Log.hpp"using namespace LogModule;
using task_t = std::function<void()>;void Download()
{LOG(LogLevel::DEBUG) << "I am a download task...";// sleep(3);
}
// Main.cc#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"using namespace LogModule;
using namespace ThreadPoolModule;
int main()
{Enable_Console_Log_Strategy();ThreadPool<task_t> *tp = new ThreadPool<task_t>();tp->Start();sleep(5);int count = 10;while (count){tp->Enqueue(Download);sleep(1);count--;}tp->Stop();tp->Join();return 0;
}

        在主程序中,主线程循环向任务队列中放入下载任务,然后唤醒线程池中休眠的线程进行任务处理。在处理完之后停止线程池,然后对线程池中的线程进行回收。 

        这里因为任务处理的过程时间很短,所以在下一个任务进入的时候,上一个处理任务的线程处理完任务就进行了休眠,导致打印出来的日志是唤醒一个线程,处理一个任务。当处理任务的时间消耗较长时,线程进入休眠的概率就会下降。 

3.2 线程安全的单例模式线程池的设计

3.2.1 单例模式

3.2.1.1 概念

        单例模式确保一个类只有一个实例存在,同时提供一个全局访问点,让其他代码可以方便地获取到这个唯一实例。在很多情况下,我们只需要一个实例来协调系统中的各项操作,比如配置文件管理、数据库连接池、日志记录器等,使用单例模式可以避免多个实例带来的资源浪费和数据不一致问题

3.2.1.2 设计原理

        (1)私有构造函数:单例类的构造函数被设置为私有,防止了外部代码通过 new 关键字来创建该类的实例

        (2)静态实例变量:在单例类内部定义一个静态的实例变量,用于保存该类的唯一实例。

        (3)静态访问方法:提供一个静态的公共方法,用于获取该类的唯一实例。在这个方法中,会检查实例是否已经创建,如果未创建则创建一个新实例,若已创建则直接返回该实例。

3.2.2 饿汉实现方式

        饿汉式单例在类加载时就创建了实例,因此它是线程安全的。但是如果这个实例在程序运行中没有进行使用,就会造成资源的浪费

         下列的实现增加了一个 _stop_flag 标记位,在调用 Stop() 时置为1,防止后续在调用其他函数获取单例的时候,调用 Start() 函数重新将 _isrunning 标记位置为 true。在 Start() 中对_stop_flag 标记位进行判断,如果为真,则再次获取单例的时候,不在将 _isrunning 标记位置为true.

// 饿汉式单例模式线程池
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;static const int gnum = 5;  // 使用全局变量来表示一个线程池默认的线程数量template <typename T>   // 使用模版的方式使线程池支持多类型的任务class ThreadPool{private:void WakeUpAllThread(){if (_sleep_num)_cond.Broadcast();LOG(LogLevel::DEBUG) << "唤醒所有休眠线程";}void WakeOne(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠的线程";}// 私有化构造函数ThreadPool(int num = gnum) : _num(num),_isrunning(false),_sleep_num(0),_stop_flag(false){for (int i = 0; i < _num; i++){_threads.emplace_back([this](){ HandlerTask(); }); // 调用线程的构造函数,线程的构造函数形参是一个回调函数}}void Start(){if (_isrunning)return;if (_stop_flag)return;_isrunning = true;for (auto &thread : _threads){thread.Start();}}// 禁用拷贝构造和赋值运算符ThreadPool(const ThreadPool<T> &) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;public:static ThreadPool<T> *GetInstance(){inc.Start();return &inc;}void HandlerTask(){char name[64];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;// 处理任务{LockGuard lockGuard(_mutex);// 1. 队列为空,线程池没有退出,进行休眠while (_taskq.empty() && _isrunning){_sleep_num++;LOG(LogLevel::INFO) << name << " 进入休眠";_cond.Wait(_mutex);_sleep_num--;}// 2. 任务为空,线程池退出,则该线程退出if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出, 因为线程池退出&&任务队列为空";break;}// 3. 获取任务t = _taskq.front();_taskq.pop();}t(); // 4. 处理任务// LOG(LogLevel::DEBUG) << name << " is running";}}bool Enqueue(const T &in){if (_isrunning) // 如果线程池停止,则停止入任务{LockGuard lockGuard(_mutex);_taskq.push(in);if (_threads.size() == _sleep_num)  // 如果全部线程都在休眠,则唤醒一个线程WakeOne();return true;}return false;}void Stop(){// 1. 将运行标志位置为falseLockGuard lockGuard(_mutex);if (!_isrunning)return;_isrunning = false;_stop_flag = true;// 2. 唤醒休眠的线程,然后再HandlerTask中进行退出WakeUpAllThread();}void Join(){for (auto &thread : _threads){thread.Join();LOG(LogLevel::INFO) << thread.GetName() << " 被Join";}}~ThreadPool(){}private:std::vector<Thread> _threads;int _num; // 线程数量std::queue<T> _taskq;   // 任务队列Cond _cond;Mutex _mutex;bool _isrunning;int _sleep_num;int _stop_flag;static ThreadPool<T> inc;  // 单例};template<typename T>ThreadPool<T> ThreadPool<T>::inc;    // 静态成员变量需要在类外进行初始化
}

3.2.3 懒汉实现方式

        懒汉式单例在第一次调用静态访问方法时才创建实例,避免了不必要的资源浪费。不过在多线程环境下,这种实现方式不是线程安全的,可能会创建多个实例。需要通过使用互斥量确保在多线程环境下只会创建一个实例懒汉方式最核心的思想是“延时加载”,从而能够优化服务器的启动速度。

        在类中增加一个静态互斥量,并在获取单例的时候进行双层判断,保证在多线程场景下使用单例模式的线程池时不会创建多个线程池单例。

// 懒汉式单例模式线程池#pragma once#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;static const int gnum = 5; // 使用全局变量来表示一个线程池默认的线程数量template <typename T> // 使用模版的方式使线程池支持多类型的任务class ThreadPool{private:void WakeUpAllThread(){if (_sleep_num)_cond.Broadcast();LOG(LogLevel::DEBUG) << "唤醒所有休眠线程";}void WakeOne(){_cond.Signal();LOG(LogLevel::INFO) << "唤醒一个休眠的线程";}// 私有化构造函数ThreadPool(int num = gnum): _num(num),_isrunning(false),_sleep_num(0){for (int i = 0; i < _num; i++){_threads.emplace_back([this](){ HandlerTask(); }); // 调用线程的构造函数,线程的构造函数形参是一个回调函数}}void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();}}// 禁用拷贝构造和赋值运算符ThreadPool(const ThreadPool<T> &) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;public:static ThreadPool<T> *GetInstance(){if (inc == nullptr) // 第一次创建的时候需要加锁,保证创建是原子性的{LockGuard lockGuard(_gmutex);if (inc == nullptr) // 双层判断,保证只会创建一个单例{LOG(LogLevel::DEBUG) << "首次使用, 创建单例...";inc = new ThreadPool<T>();inc->Start();}}return inc;}void HandlerTask(){char name[64];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;// 处理任务{LockGuard lockGuard(_mutex);// 1. 队列为空,线程池没有退出,进行休眠while (_taskq.empty() && _isrunning){_sleep_num++;LOG(LogLevel::INFO) << name << " 进入休眠";_cond.Wait(_mutex);_sleep_num--;}// 2. 任务为空,线程池退出,则该线程退出if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出, 因为线程池退出&&任务队列为空";break;}// 3. 获取任务t = _taskq.front();_taskq.pop();}t(); // 4. 处理任务// LOG(LogLevel::DEBUG) << name << " is running";}}bool Enqueue(const T &in){if (_isrunning) // 如果线程池停止,则停止入任务{LockGuard lockGuard(_mutex);_taskq.push(in);if (_threads.size() == _sleep_num) // 如果全部线程都在休眠,则唤醒一个线程WakeOne();return true;}return false;}void Stop(){// 1. 将运行标志位置为falseLockGuard lockGuard(_mutex);if (!_isrunning)return;_isrunning = false;// 2. 唤醒休眠的线程,然后再HandlerTask中进行退出WakeUpAllThread();}void Join(){for (auto &thread : _threads){thread.Join();LOG(LogLevel::INFO) << thread.GetName() << " 被Join";}}~ThreadPool(){}private:std::vector<Thread> _threads;int _num;             // 线程数量std::queue<T> _taskq; // 任务队列Cond _cond;Mutex _mutex;bool _isrunning;int _sleep_num;static ThreadPool<T> *inc; // 单例指针static Mutex _gmutex;      // 用于多线程场景下保护单例不被多次创建};template <typename T>ThreadPool<T> *ThreadPool<T>::inc = nullptr; // 静态成员变量需要在类外进行初始化template <typename T>Mutex ThreadPool<T>::_gmutex; // 自动调用Mutex的构造函数进行初始化
}

 

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

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

相关文章

量子力学:量子通信

量子通信是利用量子力学原理对信息进行编码、传输和处理的新型通信方式&#xff0c;以下是其详细介绍及业界发展现状&#xff1a; 基本原理 量子叠加态 &#xff1a;量子系统可以处于多个状态的叠加&#xff0c;如光子的偏振方向可以同时处于水平和垂直方向的叠加态&#xff…

企业架构之旅(1):TOGAF 基础入门

大家好&#xff0c;我是沛哥儿。今天我们简单聊下TOGAF哈。 文章目录 一、TOGAF 是什么定义与核心定位发展历程与行业地位与其他架构框架的区别 二、TOGAF 核心价值企业数字化转型助力业务与 IT 的协同作用降本增效与风险管控 三、TOGAF 基础术语解析架构域&#xff08;业务、…

CSS 内容超出显示省略号

CSS 内容超出显示省略号 文章目录 CSS 内容超出显示省略号**1. 单行文本省略&#xff08;常用&#xff09;****2. 多行文本省略&#xff08;如 2 行&#xff09;****3. 对非块级元素生效****完整示例****注意事项** 在 CSS 中实现内容超出显示省略号&#xff0c;主要通过控制文…

路由器重分发(OSPF+RIP),RIP充当翻译官,OSPF充当翻译官

路由器重分发&#xff08;OSPFRIP&#xff09; 版本 1 RIP充当翻译官 OSPF路由器只会OSPF语言&#xff1b;RIP路由器充当翻译官就要会OSPF语言和RIP语言&#xff1b;则在RIP中还需要将OSPF翻译成RIPOSPF 把RIP路由器当成翻译官&#xff0c;OSPF路由器就只需要宣告自己的ip&am…

AlexNet网络搭建

AlexNet网络模型搭建 环境准备 首先在某个盘符下创建一个文件夹&#xff0c;就叫AlexNet吧&#xff0c;用来存放源代码。 然后新建一个python文件&#xff0c;就叫plot.py吧&#xff0c;往里面写入以下代码&#xff0c;用于下载数据集&#xff1a; # FashionMNIST里面包含了…

【计算机网络】网络基础概念

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 这是博主计算机网络的第一篇文章&#xff0c;本文由于是基础概念了解&#xff0c;引用了大…

在Spring Boot项目中实现Word转PDF并预览

在Spring Boot项目中实现Word转PDF并进行前端网页预览&#xff0c;你可以使用Apache POI来读取Word文件&#xff0c;iText或Apache PDFBox来生成PDF文件&#xff0c;然后通过Spring Boot控制器提供文件下载或预览链接。以下是一个示例实现步骤和代码&#xff1a; 1. 添加依赖 …

图解 Redis 事务 ACID特性 |源码解析|EXEC、WATCH、QUEUE

写在前面 Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务功能。Redis的事务是将多个命令请求打包&#xff0c;然后一次性、按照顺序的执行多个命令的机制&#xff0c;并且在事务执行期间&#xff0c;服务器不会中断事务而该去执行其他客户端的命令请求。 就像下面这样&#…

LeetCode --- 446 周赛

题目列表 3522. 执行指令后的得分 3523. 非递减数组的最大长度 3524. 求出数组的 X 值 I 3525. 求出数组的 X 值 II 一、执行指令后的得分 照着题目要求进行模拟即可&#xff0c;代码如下 // C class Solution { public:long long calculateScore(vector<string>&…

山东大学软件学院项目实训-基于大模型的模拟面试系统-前端美化滚动条问题

模拟面试界面左侧底部 通过检查工具定位到其所在的位置&#xff1a; 直接对该组件进行美化&#xff1a; <!-- AI面试官列表 --><div class"ai-interviewer-section" v-show"activeTab interviewer"><el-scrollbar class"no-horizont…

git版本回退 | 远程仓库的回退 (附实战Demo)

目录 前言1. 基本知识2. Demo3. 彩蛋 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器&#xff0c;无代码爬取&#xff0c;就来&#xff1a;bright.cn 本身暂存区有多个文件&#xff0c;但手快了&…

什么事Nginx,及使用Nginx部署vue项目(非服务器Nginx压缩包版)

什么是 Nginx? Nginx(发音为 “engine-x”)是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。它以其高性能、高并发处理能力和低资源消耗而闻名。以下是 Nginx 的主要特性和用途: 主要特性 高性能和高并发 Nginx 能够处理大量并发连接,适合高…

第十六周蓝桥杯2025网络安全赛道

因为只会web&#xff0c;其他方向都没碰过&#xff0c;所以只出了4道 做出来的&#xff1a; ezEvtx 找到一个被移动的文件&#xff0c;疑似被入侵 提交flag{confidential.docx}成功解出 flag{confidential.docx} Flowzip 过滤器搜索flag找到flag flag{c6db63e6-6459-4e75-…

高性能的开源网络入侵检测和防御引擎:Suricata介绍

一、Debian下使用Suricata 相较于Windows&#xff0c;Linux环境对Suricata的支持更加完善&#xff0c;操作也更为便捷。 1. 安装 Suricata 在Debian系统上&#xff0c;你可以通过包管理器 apt 轻松安装 Suricata。 更新软件包列表: sudo apt update安装 Suricata: sudo apt …

IP-address-space

导航 (返回顶部) 1. IPv4地址分配表 1.2 IPv4 专用地址注册表1.3 各国IPv4地址分配列表 2. IPv6地址分配表 2.1 IANA IPv6 专用地址注册表2.2 IPv6 多播地址分配 1. IPv4地址分配表1.2 IPv4 专用地址注册表1.3 各国IPv4地址分配列表 2. IPv6地址分配表2.1 IANA IPv6 专用地址…

Ubuntu使用war包部署Jenkins并通过systemcl管理

目录 一、当前系统环境 二、安装Java 二、安装Jenkins 三、使用systemctl管理 一、当前系统环境 操作系统&#xff1a;ubuntu 24.04 Jenkins版本&#xff1a;2.506 格式&#xff1a;war JDK版本&#xff1a;OpenJDK_17 二、安装Java 1.下载jdk安装包 # wget下载 wget …

牛客 verilog入门 VIP

1、输出1 答案&#xff1a; timescale 1ns/1nsmodule top_module(output wire one );assign one 1b1; endmodule 2、wire连线 答案&#xff1a; timescale 1ns/1nsmodule wire0(input wire in0,output wire out1 );assign out1 in0; endmodule 3、多wire连线 timescale 1…

简易版2D我的世界C++程序(有点BUG,但是可以玩!!!)

1、按空格键来切换模式&#xff08;挖掘模式和放置模式&#xff09;&#xff0c;一律用鼠标右键来操作&#xff01;&#xff01;&#xff01; 2、按数字1和2键来切换放置的方块&#xff08;1是草&#xff0c;2是木&#xff09;&#xff0c;树叶不能放置&#xff01;&#xff01…

ubuntu使用dify源码安装部署教程+避坑指南

很多人,包括我在最初使用dify的时候都习惯使用docker来部署安装环境,但在二次开发使用过程中,我们可能希望使用源码来安装,那么这篇文章我将给大家分享如何在ubuntu系统下使用源码安装,并提供大家遇到的疑难杂症如下: dify安装使用过程中报错:/console/api/workspaces/…

java知识体系结构导航

很全&#xff1a;java知识体系结构 个人笔记链接 开发工具IDEA IDEA 插件推荐清单 IDEA快捷键大全 Java基础难点 基础知识_java动态代理 基础知识_java反射机制 基础知识-java流steam 基础知识-java集合collection Spring 01.Spring 框架的演化&#xff1a;从 XML 配置到…