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.pswp.cn/web/77416.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77416.shtml
英文地址,請注明出處:http://en.pswp.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 配置到…