目錄
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的構造函數進行初始化
}
?