Linux線程池實現

1.線程池實現

全部代碼:whb-helloworld/113

1.喚醒線程

一個是喚醒全部線程,一個是喚醒一個線程。

void WakeUpAllThread(){LockGuard lockguard(_mutex);if (_sleepernum)_cond.Broadcast();LOG(LogLevel::INFO) << "喚醒所有的休眠線程";}void WakeUpOne(){_cond.Signal();LOG(LogLevel::INFO) << "喚醒一個休眠線程";}

2.創建線程

全局變量gnum的值就是要創建的線程的個數,然后把lambda表達式插入進去,Thread里面有function可以接收這個表達式,然后執行routine函數時就會執行這個表達式。emplace_back一個表達式,本質就是把這個表達式復制給這個vector<Thread>的Thread類的元素,就是傳參過去。

 ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(0){for (int i = 0; i < num; i++){_threads.emplace_back([this](){HandlerTask();});}}
 using func_t = std::function<void()>;static void *Routine(void *args) // 屬于類內的成員函數,默認包含this指針!{Thread *self = static_cast<Thread *>(args);self->EnableRunning();if (self->_isdetach)self->Detach();pthread_setname_np(self->_tid, self->_name.c_str());self->_func(); // 回調處理return nullptr;}

?

在C++中,捕獲 this 是一種常見的做法,尤其是在使用 Lambda 表達式時。捕獲 this 的主要目的是為了讓 Lambda 表達式能夠訪問類的成員變量和成員函數。

3.開始函數

判斷是否運行,沒有就把狀態變為true,然后范圍for把線程都啟動,并打印日志信息

 void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();}}

?4.關掉拷貝和賦值

ThreadPool(const ThreadPool<T> &) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

5.單例模式

?先判斷是否創建了實例,沒有就加鎖,第二個判斷也是判斷是否有實例,沒有就創建并執行開始函數,最后返回inc這個指向實例的指針。第一個判斷是為了下次不用一直加鎖,只要保證第一次實例加鎖就行,后面就可以不用加鎖了,提高效率。

static ThreadPool<T> *GetInstance(){if (inc == nullptr){LockGuard lockguard(_lock);LOG(LogLevel::DEBUG) << "獲取單例....";if (inc == nullptr){LOG(LogLevel::DEBUG) << "首次使用單例, 創建之....";inc = new ThreadPool<T>();inc->Start();}}return inc;}

?6.停止函數和回收函數

根據_isrunning判斷狀態,如果是運行狀態,就變為false,然后喚醒所有線程開始執行任務,

join就回收這些線程的信息。

 void Stop(){if (!_isrunning)return;_isrunning = false;// 喚醒所有的線層WakeUpAllThread();}void Join(){for (auto &thread : _threads){thread.Join();}}

?

7.處理任務函數

開始是把線程名字放到name里面,進到循環里面,要加鎖保證sleepernum值不會因為并發而改變,循環里面第一個循環只有任務隊列為空且線程是運行才會進到等待隊列進行休眠,sleep值是計算休眠的個數,判斷是線程不運行且任務隊列為空就退出了,沒退出就說明有任務,就取出隊列的任務給t,最后就可以t()執行任務。

void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;{LockGuard lockguard(_mutex);// 1. a.隊列為空 b. 線程池沒有退出while (_taskq.empty() && _isrunning){_sleepernum++;_cond.Wait(_mutex);_sleepernum--;}// 2. 內部的線程被喚醒if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出了, 線程池退出&&任務隊列為空";break;}// 一定有任務t = _taskq.front(); // 從q中獲取任務,任務已經是線程私有的了!!!_taskq.pop();}t(); // 處理任務,需/要在臨界區內部處理嗎?1 0}}
  1. pthread_getname_np(pthread_self(), name, sizeof(name));

    • pthread_getname_np 是一個 POSIX 線程庫(pthread)提供的函數,用于獲取線程的名稱。

    • pthread_self() 是一個函數,返回當前線程的線程ID(pthread_t 類型)。

    • name 是目標數組,用于存儲線程名稱。

    • sizeof(name) 是目標數組的大小,確保不會超出數組的存儲范圍。

8.入任務函數

判斷狀態,運行就進去加鎖,插入數據加鎖避免并發插入數據,在判斷線程大小和休眠個數是否相等,一樣就說明都休眠了,要喚醒線程進行處理任務。

bool Enqueue(const T &in){if (_isrunning){LockGuard lockguard(_mutex);_taskq.push(in);if (_threads.size() == _sleepernum)WakeUpOne();return true;}return false;}

9.完整代碼

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"// .hpp header onlynamespace ThreadPoolModule
{using namespace ThreadModlue;using namespace LogModule;using namespace CondModule;using namespace MutexModule;static const int gnum = 5;template <typename T>class ThreadPool{private:void WakeUpAllThread(){LockGuard lockguard(_mutex);if (_sleepernum)_cond.Broadcast();LOG(LogLevel::INFO) << "喚醒所有的休眠線程";}void WakeUpOne(){_cond.Signal();LOG(LogLevel::INFO) << "喚醒一個休眠線程";}ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(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();LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();}}ThreadPool(const ThreadPool<T> &) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;public:static ThreadPool<T> *GetInstance(){if (inc == nullptr){LockGuard lockguard(_lock);LOG(LogLevel::DEBUG) << "獲取單例....";if (inc == nullptr){LOG(LogLevel::DEBUG) << "首次使用單例, 創建之....";inc = new ThreadPool<T>();inc->Start();}}return inc;}void Stop(){if (!_isrunning)return;_isrunning = false;// 喚醒所有的線層WakeUpAllThread();}void Join(){for (auto &thread : _threads){thread.Join();}}void HandlerTask(){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));while (true){T t;{LockGuard lockguard(_mutex);// 1. a.隊列為空 b. 線程池沒有退出while (_taskq.empty() && _isrunning){_sleepernum++;_cond.Wait(_mutex);_sleepernum--;}// 2. 內部的線程被喚醒if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出了, 線程池退出&&任務隊列為空";break;}// 一定有任務t = _taskq.front(); // 從q中獲取任務,任務已經是線程私有的了!!!_taskq.pop();}t(); // 處理任務,需/要在臨界區內部處理嗎?1 0}}bool Enqueue(const T &in){if (_isrunning){LockGuard lockguard(_mutex);_taskq.push(in);if (_threads.size() == _sleepernum)WakeUpOne();return true;}return false;}~ThreadPool(){}private:std::vector<Thread> _threads;int _num; // 線程池中,線程的個數std::queue<T> _taskq;Cond _cond;Mutex _mutex;bool _isrunning;int _sleepernum;// bug??static ThreadPool<T> *inc; // 單例指針static Mutex _lock;};template <typename T>ThreadPool<T> *ThreadPool<T>::inc = nullptr;template <typename T>Mutex ThreadPool<T>::_lock;}

2.單例模式與設計模式

類里面static修飾變量,則變量是屬于類的,而不是屬于某個特定對象,無論創建多少個對象,static變量都只有一份,所有對象共享一份static變量。

static修飾方法,則方法也是屬于類的,不能訪問非靜態成員(變量和方法),因為非靜態成員需要對象才能訪問,所有對象共享一個static方法

而鎖加static,是為了保證唯一鎖,不然創建多個鎖沒有static,則每個鎖都是不一樣的,就等于一個門上有很多鑰匙孔,static就可以保證只有一個鑰匙孔和一把鑰匙。

單例模式

單例模式是屬于一種創建型設計模式,確保一個類在整個應用程序中只有一個實例,提供一個全局訪問點來訪問這個實例這個實例。

餓漢模式

餓漢模式在類被加載時就立即創建實例,而不是在第一次需要實例時才創建。這種方式特定是實例創建早,通長用于資源不多且創建實例開銷較小的情況,因為每次執行都要創建一個實例,如果實例體積大就會占用很多內存,所以適用于資源不多的。

餓漢模式特點

立即實例化:類加載時就創建了單例實例,不管是否有其它地方需要這個實例

線程安全:由于實例在加載時就創建好了,線程安全問題得到天然的解決

浪費資源:單例對象的創建開銷大,且開辟的空間不一定用的上,會有低效率

當兩個線程同時訪問一個類,并且這個類采用立即實例化的方式創建單例時,由于實例化是在類加載時完成的,所以實際上不存在兩個線程同時創建實例的情況。下面是具體的原因和過程:

1. **類加載機制**:在Java中,類加載是由類加載器完成的。類加載器會保證一個類只被加載一次。當第一個線程觸發類的加載時,類加載器會同步這個加載過程,確保其他線程在當前線程完成加載之前不會進入類的加載過程。

2. **靜態初始化**:在類加載的過程中,如果類中包含靜態初始化塊或者靜態變量初始化,這些操作會在類加載時執行。如果單例實例是在靜態初始化塊中創建的,那么這個實例的創建過程是同步的,即在任何線程看到類的加載完成之前,實例已經被創建好了。

3. **線程安全保證**:由于類加載和靜態初始化是同步的,這意味著當第一個線程觸發類的加載并創建實例時,其他線程會被阻塞,直到實例創建完成。因此,不會有多個線程同時創建實例的情況發生。

4. **可見性**:在Java中,靜態變量的初始化具有可見性保證。一旦靜態變量被初始化,它對所有線程都是可見的。這意味著一旦單例實例被創建,所有線程都可以看到它。

總結來說,即使在多線程環境中,由于類加載和靜態初始化的同步機制,以及靜態變量的可見性保證,立即實例化的單例模式可以確保在任何時候都只有一個實例被創建,從而保證了線程安全。這就是為什么即使有兩個線程同時訪問一個采用立即實例化的類,也不會導致線程安全問題的原因。

餓漢模式單例實現

template<typename T>
class Singleton
{static T data;
public:static T* GetInstance(){return &data;}
};

懶漢模式

是一種單例模式的實現方式,就需要在第一次實例時創建對象。特點是推遲實例化,從而避免對不必要的資源消耗。

懶漢單例模式特點

延遲實例化:對象僅在第一次被請求時才進行創建,從而提高程序的效率

資源節省:在不需要實例的情況下,避免了資源的浪費,優化了資源的使用

線程安全問題:在多線程環境下,可能需要額外的同步機制來確保線程安全問題,確保實例的唯一性。

?懶漢單例模式的實現

template<typename T>
class Singleton
{static T* inst;
public:static T* GetInstance(){if (inst == nullptr){inst = new T();}return inst;}
};

上面線程池以懶漢模式實現

3.線程安全和重入問題

概念
線程安全:就是多個線程在訪問共享資源時,能夠正確地執?,不會相互?擾或破壞彼此的執?結
果。?般??,多個線程并發同?段只有局部變量的代碼時,不會出現不同的結果。但是對全局變量 或者靜態變量進?操作,并且沒有鎖保護的情況下,容易出現該問題。
重?:同?個函數被不同的執?流調?,當前?個流程還沒有執?完,就有其他的執?流再次進?, 我們稱之為重?。?個函數在重?的情況下,運?結果不會出現任何不同或者任何問題,則該函數被 稱為可重?函數,否則,是不可重?函數。
常?的線程不安全的情況
?
不保護共享變量的函數
?
函數狀態隨著被調?,狀態發?變化的函數
?
返回指向靜態變量指針的函數
?
調?線程不安全函數的函數
常?不可重?的情況
?
調?了malloc/free函數,因為malloc函數
是?全局鏈表來管理堆的
?
調?了標準I/O庫函數,標準I/O庫的很多實
現都以不可重?的?式使?全局數據結構
?
可重?函數體內使?了靜態的數據結構
常?的線程安全的情況
?
每個線程對全局變量或者靜態變量只有讀取
的權限,?沒有寫?的權限,?般來說這些
線程是安全的
?
類或者接?對于線程來說都是原?操作
?
多個線程之間的切換不會導致該接?的執?
結果存在?義性
常?可重?的情況
?
不使?全局變量或靜態變量
?
不使? malloc或者new開辟出的空間
?
不調?不可重?函數
?
不返回靜態或全局數據,所有數據都有函數
的調?者提供
?
使?本地數據,或者通過制作全局數據的本
地拷?來保護全局數據
可重?與線程安全聯系
?
函數是可重?的,那就是線程安全的(其實知道這?句話就夠了)
?
函數是不可重?的,那就不能由多個線程使?,有可能引發線程安全問題
?
如果?個函數中有全局變量,那么這個函數既不是線程安全也不是可重?的。
可重?與線程安全區別
可重?函數是線程安全函數的?種
線程安全不?定是可重?的,?可重?函數則?定是線程安全的。
如果將對臨界資源的訪問加上鎖,則這個函數是線程安全的,但如果這個重?函數若鎖還
未釋放則會產?死鎖,因此是不可重?的。
注意:
如果不考慮信號導致一個執行流重復進入函數這種情況,線程安全和重入在安全角度不做區分,但是線程安全側重說明線程訪問公共資源的安全問題情況,表項的是并發線程的特點,可重入描述的是一個函數是否能被重復進入,表示的是函數的特點。

4.常見鎖的概念

死鎖是指在?組進程中的各個進程均占有不會釋放的資源,但因互相申請被其他進程所站?不會
釋放的資源?處于的?種永久等待狀態。
?
為了?便表述,假設現在線程A,線程B必須同時持有鎖1和鎖2,才能進?后續資源的訪問

?

?死鎖的四個必要條件

互斥條件:一個資源每次只能被一個執行流使用

請求與保持條件:一個執行流因請求資源而阻塞,對已獲得的資源保持不放

?

5.STL,智能指針和線程安全

?STL的容器不是線程安全的,STL的設計初衷是將性能挖掘到極致,而一旦設計到加鎖保證線程安全,會對性能造成巨大影響,而且對于不同的容器,加鎖的方式不同。這樣STL默認是線程不安全的,需要自己保證線程安全。

智能指針unique_ptr,由于只是在當前代碼塊內生效,不涉及線程安全問題。

對于shared_ptr,多個對象需要共用一個引用計數變量,就會存在線程安全問題。標準庫解決了這個問題,基于原子操作的(CAS)的方式保證了shared_ptr能夠高效,原子的操作引用計數。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/73951.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73951.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/73951.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

微信小程序逆向開發

一.wxapkg文件 如何查看微信小程序包文件&#xff1a; 回退一級 點擊進入這個目錄 這個就是我們小程序對應的文件 .wxapkg概述 .wxapkg是微信小程序的包文件格式&#xff0c;且其具有獨特的結構和加密方式。它不僅包含了小程序的源代碼&#xff0c;還包括了圖像和其他資源文…

多輸入多輸出 | Matlab實現CPO-LSTM冠豪豬算法優化長短期記憶神經網絡多輸入多輸出預測

多輸入多輸出 | Matlab實現CPO-LSTM冠豪豬算法優化長短期記憶神經網絡多輸入多輸出預測 目錄 多輸入多輸出 | Matlab實現CPO-LSTM冠豪豬算法優化長短期記憶神經網絡多輸入多輸出預測預測效果基本介紹程序設計參考資料 預測效果 基本介紹 Matlab實現CPO-LSTM冠豪豬算法優化長短期…

視頻編碼器的抉擇:x264、x265、libaom、vvenc 對比測試實驗

264、x265、libaom、vvenc 對比測試實驗 測試機器配置&#xff1a;Apple M1 Pro -16G編碼器版本&#xff08;選擇自己編譯&#xff09;&#xff1a;所有源碼都是當前最新更新的狀態&#xff0c;此外各類編碼具體的編譯過程可參考我的相關系列博客。 編碼器GitHubx264git clon…

【二刷代碼隨想錄】雙指針-數組相關題型、推薦習題

一、雙指針-數組 相關題型與常用思路 1、單個數組 &#xff08;1&#xff09;原地移除元素類 如推薦習題中的&#xff08;1&#xff09;、&#xff08;2&#xff09;、&#xff08;3&#xff09;&#xff0c;都屬于此類。引入雙指針 pre、last &#xff0c;用 pre 指針表明數…

Level DB --- TableCache

TableCache 是Level DB 中重要的類&#xff0c;Level DB 中多層&#xff08;multi level&#xff09;&#xff0c;且每一層&#xff08;level&#xff09;有多個 key-value file&#xff0c;TableCache正是用來緩存多層以及多層中的file數據&#xff0c;更快速地檢索。 table …

搜索-BFS

馬上藍橋杯了&#xff0c;最近刷了廣搜&#xff0c;感覺挺有意思的&#xff0c;廣搜題類型都差不多&#xff0c;模板也一樣&#xff0c;大家寫的時候可以直接套模板 這里給大家講一個比較經典的廣搜題-迷宮 題目問問能否走到 (n,m) 位置&#xff0c;假設最后一個點是我們的&…

智能預測維護:讓設備“未卜先知”,減少宕機煩惱

智能預測維護:讓設備“未卜先知”,減少宕機煩惱 1. 引言:設備維護的痛點與出路 在工業生產和自動化領域,設備故障一直是令人頭疼的問題。設備一旦故障,輕則影響生產效率,重則造成嚴重損失,甚至帶來安全隱患。傳統的設備維護方式主要有兩種: 被動維護(Reactive Maint…

安卓的布局方式

一、RelativeLayout 相對布局 特點&#xff1a;每個組件相對其他的某一個組件進行定位。 (一)主要屬性 1、設置和父組件的對齊&#xff1a; alignParentTop &#xff1a; 設置為true&#xff0c;代表和父布局頂部對齊。 其他對齊只需要改變后面的Top為 Left、Right 或者Bottom&…

SSM中藥分類管理系統

&#x1f345;點贊收藏關注 → 添加文檔最下方聯系方式咨詢本源代碼、數據庫&#x1f345; 本人在Java畢業設計領域有多年的經驗&#xff0c;陸續會更新更多優質的Java實戰項目希望你能有所收獲&#xff0c;少走一些彎路。&#x1f345;關注我不迷路&#x1f345; 項目視頻 SS…

epoch、batch、batch size、step、iteration深度學習名詞含義詳細介紹

卷積神經網絡訓練中的三個核心概念&#xff1a;Epoch、Batch Size 和迭代次數 在深度學習中&#xff0c;理解一些基本的術語非常重要&#xff0c;這些術語對模型的訓練過程、效率以及最終性能都有很大影響。以下是一些常見術語的含義介紹&#xff1a; 1. Epoch&#xff08;周…

React(七):Redux

Redux基本使用 純函數&#xff1a;1.函數內部不能依賴函數外部變量&#xff1b;2.不能產生副作用&#xff0c;在函數內部改變函數外部的變量 React只幫我們解決了DOM的渲染過程&#xff0c;State還是要由我們自己來管理——redux可幫助我們進行管理 Redux三大特點 1.單一數…

《Android低內存設備性能優化實戰:深度解析Dalvik虛擬機參數調優》

1. 痛點分析&#xff1a;低內存設備的性能困局 現象描述&#xff1a;大應用運行時頻繁GC導致卡頓 根本原因&#xff1a;Dalvik默認內存參數與硬件資源不匹配 解決方向&#xff1a;動態調整堆內存參數以平衡性能與資源消耗 2. 核心調優參數全景解析 關鍵參數矩陣&#xff1…

STC89C52單片機學習——第38節: [17-2] 紅外遙控紅外遙控電機

寫這個文章是用來學習的,記錄一下我的學習過程。希望我能一直堅持下去,我只是一個小白,只是想好好學習,我知道這會很難&#xff0c;但我還是想去做&#xff01; 本文寫于&#xff1a;2025.03.30 51單片機學習——第38節: [17-2] 紅外遙控&紅外遙控電機 前言開發板說明引用…

計算機組成原理————計算機運算方法精講<1>原碼表示法

第一部分:無符號數和有符號數的概念 1.無符號數 計算機中的數均存放在寄存器當中,通常稱寄存器的位數為機器字長,所謂無符號數,就是指沒有fu5號的數,在寄存器中的每一位均可用來存放數值,當存放有符號數時,需要留出位置存放符號,機器字長相同時,無符號數與有符號數所…

【什么是機器學習——多項式逼近】

什么是機器學習——多項式逼近 機器學習可以分成三大類別,監督學習、非監督學習、強化學習。三大類別背后的數學原理不同。監督學習使用了數學分析中的函數逼近方法和概率統計中的極大似然方法;非監督學習使用聚類和EM算法;強化學習使用馬爾可夫決策過程的想法。 機器學習的…

Ubuntu 22.04 上安裝阿里云 CLI(命令行工具)

在 Ubuntu 22.04 上安裝阿里云 CLI&#xff08;命令行工具&#xff09;可以通過以下步驟完成&#xff1a; 步驟 1&#xff1a;下載阿里云 CLI 安裝包 打開終端&#xff0c;首先更新你的軟件包索引&#xff1a; sudo apt update安裝 curl&#xff08;如果還沒有安裝&#xff09…

?Android Gradle 插件(AGP)版本與 ?Gradle 版本需要嚴格對應

一、AGP 與 Gradle 版本對照表 Android Gradle 插件版本對應 Gradle 版本適用 Android Studio 版本?8.1.x8.2Arctic Fox (2020.3.1+)?8.0.x8.0Arctic Fox (2020.3.1+)?7.4.x7.5.1IntelliJ IDEA 2022+?7.3.x7.4IntelliJ IDEA 2022+?7.2.x7.3.3IntelliJ IDEA 2021.3+?7.1.x…

【Matlab】-- 基于MATLAB的灰狼算法優化支持向量機的回歸算法

文章目錄 文章目錄 01 內容概要02 GWO-SVR模型03 部分代碼04 運行結果05 參考文獻06 代碼下載 01 內容概要 GWOSVR&#xff08;基于灰狼算法優化的支持向量機回歸&#xff09;是一種先進的機器學習技術&#xff0c;它結合了灰狼優化算法&#xff08;Grey Wolf Optimizer, GWO…

Google Play Games PC版即將正式上線!

早在 2021 年&#xff0c;谷歌就推出 Google Play Games PC 版&#xff0c;本質上是基于虛擬化創建安卓系統在 Windows 上運行 Google Play 平臺的各種游戲。 在測試了 4 年后&#xff0c;谷歌準備在今年晚些時候正式上線該平臺&#xff0c;谷歌將在下周舉辦 2025 游戲開發者大…

【SpringBoot】深入解析使用配置文件解決硬編碼問題綜合練習(三):解析驗證碼拓展問題

校驗輸入驗證碼接口 check( ) 5. 為什么要用靜態內部類接收配置文件中的 Seisson 對象&#xff1f; 為什么我們接收配置文件的 Session 對象時&#xff0c;使用靜態內部類給 Session 對象的 key&#xff0c;date 屬性賦值呢&#xff1f;不加 static 可以嗎&#xff1f; 在 Cap…