Linux筆記---單例模式與線程池

1. 單例模式

單例模式是一種常用的設計模式,它確保一個類只有一個實例,并提供一個全局訪問點來獲取這個實例。這種模式在需要控制資源訪問、管理共享狀態或協調系統行為時非常有用。

單例模式的核心特點

  • 私有構造函數:防止外部通過new關鍵字創建實例;
  • 靜態成員函數:提供全局訪問點(靜態成員函數getInstance());
  • 禁止拷貝和賦值:通過delete關鍵字禁用拷貝構造函數和賦值運算符。

單例模式的應用場景

  • 日志系統:確保所有日志都寫入同一個日志文件;
  • 配置管理:全局共享一份配置信息;
  • 數據庫連接池:統一管理數據庫連接;
  • 設備管理器:如打印機等硬件設備的管理。

需要注意的是,單例模式雖然方便,但也會帶來一些問題,如增加代碼耦合度、不利于單元測試等,因此在使用時需要權衡利弊。

在 C++ 中,單例模式主要有兩種實現方式:餓漢式懶漢式,它們的核心區別在于實例創建的時機不同

1.1 餓漢式實現

餓漢式在程序啟動時(類加載時)就創建唯一實例,不管后續是否會使用到。

class EagerSingleton {
private:// 私有構造函數,防止外部創建實例EagerSingleton() {}// 禁用拷貝構造和賦值運算EagerSingleton(const EagerSingleton&) = delete;EagerSingleton& operator=(const EagerSingleton&) = delete;// 靜態成員變量,程序啟動時就初始化static EagerSingleton instance;public:// 提供全局訪問點static EagerSingleton& getInstance() {return instance;}
};// 在類外初始化靜態成員
EagerSingleton EagerSingleton::instance;

餓漢式特點:

  • 線程安全:由于實例在程序啟動時就創建,不存在多線程競爭問題;
  • 可能浪費資源:如果單例從未被使用,也會占用內存;
  • 實現簡單:不需要考慮線程同步問題。

1.2 懶漢式實現

懶漢式在第一次調用getInstance()方法時才創建實例,實現了 "延遲初始化"

class LazySingleton {
private:// 私有構造函數LazySingleton() {}// 禁用拷貝構造和賦值運算LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;public:// 提供全局訪問點,C++11后局部靜態變量初始化是線程安全的static LazySingleton& getInstance() {// 第一次調用時才初始化實例static LazySingleton instance;return instance;}
};

或者:

class LazySingleton {
private:// 私有構造函數LazySingleton() {}// 私有析構函數~LazySingleton() {instance = nullptr;}// 禁用拷貝構造和賦值運算LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;// 使用指針的方式來訪問唯一實例static LazySingleton *instance;public:// 此方式無法保證線程安全,應當加鎖保護static LazySingleton* const getInstance() {// 第一次調用時才初始化實例if(instance == nullptr)instance = new LazySingleton();return instance;}
};LazySingleton* LazySingleton::instance = nullptr;

前者和餓漢式一樣,實例在創建之后就不會消失后者的實例則可以在被銷毀之后重新申請

懶漢式特點:

  • 延遲初始化:節省資源,只有在真正需要時才創建實例;
  • 線程安全(C++11 及以上):標準保證局部靜態變量的初始化是線程安全的;
  • 可能影響首次訪問性能:第一次調用時需要完成初始化。

1.3 總結

餓漢式懶漢式
實例創建時機程序啟動時首次使用時
線程安全性天然安全C++11 后安全
資源利用可能浪費更高效
實現復雜度簡單稍復雜(需考慮線程安全)

實際開發中,懶漢式因為其資源利用效率更高而更常用,尤其是在單例可能不會被使用的場景下。而餓漢式適合在程序啟動時就需要初始化的核心組件。

2. 用單例模式設計線程池

2.1 什么是線程池

線程池是一種線程管理機制,它預先創建一定數量的線程,通過復用這些線程來處理多個任務,從而避免頻繁創建和銷毀線程帶來的性能開銷,提高系統效率和資源利用率

線程池包含一個線程隊列和一個任務隊列

  • 線程隊列:存儲預先創建的空閑線程,等待處理任務。
  • 任務隊列:存放待執行的任務,當線程空閑時會從隊列中獲取任務執行。

2.2 如何將任務傳遞給線程

首先,任務隊列一定是定義在線程池內部的,我們要使線程訪問到任務隊列及保護任務隊列的鎖,就要使這些線程能訪問到線程池本身。

所以,我們在線程池內部定義線程的運行函數,即不斷循環訪問任務隊列獲取任務:

void ThreadHandler()
{while (true){Task task;{LockGuard lockguard(_queue_mutex);while (_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if (_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "線程[" << Thread::GetMyName() << "]退出...";
}

但是直接將該函數傳遞給線程是不行的。例如,在構造線程時,直接將該函數以及this傳過去:

ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true)
{if (thread_num <= 0)throw ThreadPoolException("線程數量過少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), ThreadHandler, this);
}

編譯就會出現如下報錯:

這個編譯錯誤的核心原因是:非靜態成員函數(ThreadHandler)不能直接作為函數名傳遞給線程構造函數。非靜態成員函數的調用必須依賴于一個類的實例(this指針),而直接進行傳參時,編譯器無法自動關聯實例,因此判定為 “無效使用非靜態成員函數”。

讓線程執行非靜態成員函數的正確方式是使用lambda表達式捕捉this并包裝該函數:

[this] { ThreadHandler(); 
}

然后像這樣傳參:

_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });

2.3 懶漢實現1

#pragma once
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Log.hpp"using namespace MutexModule;
using namespace ThreadModule;
using namespace CondModule;
using namespace LogModule;namespace ThreadPoolModule
{class ThreadPoolException : public std::runtime_error{public:explicit ThreadPoolException(const std::string &message): std::runtime_error("ThreadPoolException: " + message) {}};static const unsigned int default_thread_num = 6;static const unsigned int default_capacity = 9;template <typename Task>class ThreadPool{private:ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true){if (thread_num <= 0)throw ThreadPoolException("線程數量過少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });}~ThreadPool(){}ThreadPool(const ThreadPool<Task>&) = delete;ThreadPool<Task>& operator=(const ThreadPool<Task>&) = delete;void ThreadHandler(){while (true){Task task;{LockGuard lockguard(_queue_mutex);while(_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if(_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "線程[" << Thread::GetMyName() << "]退出...";}void Start(){for (auto &thread : _threads)thread.Start();LOG(LogLevel::DEBUG) << "線程池已啟動...";}public:void Stop(){_isrunning = false;_not_empty.broadcast();LOG(LogLevel::DEBUG) << "線程池開始停止...";}void Wait(){if(_isrunning)throw ThreadPoolException("等待前線程池未停止!");for (auto &thread : _threads)thread.Join();LOG(LogLevel::DEBUG) << "等待成功, 線程已全部退出...";}void PushTask(const Task &task){if(!_isrunning)throw ThreadPoolException("線程池已停止, 無法繼續增加任務!");_queue.push(task);_not_empty.signal();}static ThreadPool<Task>& GetInstance(){static ThreadPool<Task> Instance;Instance.Start();return Instance;}private:bool _isrunning;std::vector<Thread> _threads;std::queue<Task> _queue;Mutex _queue_mutex;Cond _not_empty;};
}

2.4 懶漢實現2

#pragma once
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Log.hpp"using namespace MutexModule;
using namespace ThreadModule;
using namespace CondModule;
using namespace LogModule;namespace ThreadPoolModule
{class ThreadPoolException : public std::runtime_error{public:explicit ThreadPoolException(const std::string &message): std::runtime_error("ThreadPoolException: " + message) {}};static const unsigned int default_thread_num = 6;static const unsigned int default_capacity = 9;template <typename Task>class ThreadPool{private:ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true){if (thread_num <= 0)throw ThreadPoolException("線程數量過少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });}~ThreadPool(){_ins = nullptr;}ThreadPool(const ThreadPool<Task>&) = delete;ThreadPool<Task>& operator=(const ThreadPool<Task>&) = delete;void ThreadHandler(){while (true){Task task;{LockGuard lockguard(_queue_mutex);while(_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if(_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "線程[" << Thread::GetMyName() << "]退出...";}void Start(){for (auto &thread : _threads)thread.Start();LOG(LogLevel::DEBUG) << "線程池已啟動...";}public:void Stop(){_isrunning = false;_not_empty.broadcast();LOG(LogLevel::DEBUG) << "線程池開始停止...";}void Wait(){if(_isrunning)throw ThreadPoolException("等待前線程池未停止!");for (auto &thread : _threads)thread.Join();delete _ins;LOG(LogLevel::DEBUG) << "等待成功, 線程已全部退出...";}void PushTask(const Task &task){if(!_isrunning)throw ThreadPoolException("線程池已停止, 無法繼續增加任務!");_queue.push(task);_not_empty.signal();}static ThreadPool<Task> *const GetInstance(){if (_ins == nullptr){LockGuard lockguard(_mutex);if (_ins == nullptr){try{_ins = new ThreadPool<Task>();}catch (const std::exception &e){std::cerr << e.what() << '\n';}_ins->Start();}}return _ins;}private:bool _isrunning;std::vector<Thread> _threads;std::queue<Task> _queue;static ThreadPool<Task> *_ins;Mutex _queue_mutex;static Mutex _mutex;Cond _not_empty;};template <typename Task>ThreadPool<Task> *ThreadPool<Task>::_ins = nullptr;template <typename Task>Mutex ThreadPool<Task>::_mutex;
}

相比較于第一種,第二種可以在調用Stop以及Wait函數之后重新啟動。

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

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

相關文章

Linux中的指令

1.adduseradduser的作用是創立一個新的用戶。當我們在命令行中輸入1中的指令后&#xff0c;就會彈出2中的命令行&#xff0c;讓我們設立新的密碼&#xff0c;緊接著就會讓我們再次輸入新的密碼&#xff0c;對于密碼的輸入它是不會顯示出來的&#xff0c;如果輸入錯誤就會讓我們…

【n8n】Docker容器中安裝ffmpeg

容器化部署 n8n 時&#xff0c;常常會遇到一些環境依賴問題。缺少 docker 命令或無法安裝 ffmpeg 是較為常見的場景&#xff0c;如果處理不當&#xff0c;會導致流程執行受限。 本文介紹如何在 n8n 容器中解決 docker 命令不可用和 ffmpeg 安裝受限的問題&#xff0c;并給出多…

【基礎算法】初識搜索:遞歸型枚舉與回溯剪枝

文章目錄一、搜索1. 什么是搜索&#xff1f;2. 遍歷 vs 搜索3. 回溯與剪枝二、OJ 練習1. 枚舉子集 ?(1) 解題思路(2) 代碼實現2. 組合型枚舉 ?(1) 解題思路請添加圖片描述(2) 代碼實現3. 枚舉排列 ?(1) 解題思路(2) 代碼實現4. 全排列問題 ?(1) 解題思路(2) 代碼實現一、搜…

Node.js異步編程——async/await實現

一、async/await基礎語法 在Node.Js編程中,async關鍵字用于定義異步函數,這個異步函數執行完會返回一個Promise對象,異步函數的內部可以使用await關鍵字來暫停當前代碼的繼續執行,直到Promise操作完成。 在用法上,async關鍵字主要用于聲明一個異步函數,await關鍵字主要…

搭建一個簡單的Agent

準備本案例使用deepseek&#xff0c;登錄deepseek官網&#xff0c;登錄賬號&#xff0c;充值幾塊錢&#xff0c;然后創建Api key可以創建虛擬環境&#xff0c;python版本最好是3.12&#xff0c;以下是文件目錄。test文件夾中&#xff0c;放一些txt文件做測試&#xff0c;main.p…

uv,下一代Python包管理工具

什么是uv uv&#xff08;Universal Virtual&#xff09;是由Astral團隊&#xff08;知名Python工具Ruff的開發者&#xff09;推出的下一代Python包管理工具&#xff0c;使用Rust編寫。它集成了包管理、虛擬環境、依賴解析、Python版本控制等功能&#xff0c;它聚焦于三個關鍵點…

單片機的輸出模式推挽和開漏如何選擇呢?

推挽和開漏是單片機的輸出模式&#xff0c;屬于I/O口配置的常見類型。開漏&#xff08;Open-Drain&#xff09;和推挽&#xff08;Push-Pull&#xff09;是兩種根本不同的輸出電路結構&#xff0c;理解它們的區別是正確使用任何單片機&#xff08;包括51和STM32&#xff09;GPI…

java18學習筆記-Simple Web Server

408:Simple Web Server Python、Ruby、PHP、Erlang 和許多其他平臺提供從命令行運行的開箱即用服務器。這種現有的替代方案表明了對此類工具的公認需求。 提供一個命令行工具來啟動僅提供靜態文件的最小web服務器。沒有CGI或類似servlet的功能可用。該工具將用于原型設計、即…

深度解析Atlassian 團隊協作套件(Jira、Confluence、Loom、Rovo)如何賦能全球分布式團隊協作

無窮無盡的聊天記錄、混亂不堪的文檔、反饋信息分散在各個不同時區……在全球分布式團隊中開展真正的高效協作&#xff0c;就像是一場不可能完成的任務。 為什么會這樣&#xff1f;因為即使是最聰明的團隊&#xff0c;也會遇到類似的障礙&#xff1a; 割裂的工作流&#xff1a…

理解AI 智能體:智能體架構

1. 引言 智能體架構&#xff08;agent architecture&#xff09;是一份藍圖&#xff0c;它定義了AI智能體各組件的組織方式和交互機制&#xff0c;使智能體能夠感知環境、進行推理并采取行動。本質上&#xff0c;它就像是智能體的數字大腦——整合了“眼睛”&#xff08;傳感器…

Spring Cloud系列—SkyWalking鏈路追蹤

上篇文章&#xff1a; Spring Cloud系列—Seata分布式事務解決方案TCC模式和Saga模式https://blog.csdn.net/sniper_fandc/article/details/149947829?fromshareblogdetail&sharetypeblogdetail&sharerId149947829&sharereferPC&sharesourcesniper_fandc&…

機器人領域的算法研發

研究生期間學習大模型&#xff0c;可投遞機器人領域的算法研發、技術支持等相關崗位&#xff0c;以下是具體推薦&#xff1a; AI算法工程師&#xff08;大模型方向-機器人應用&#xff09;&#xff1a;主要負責大模型開發與優化&#xff0c;如模型預訓練、調優及訓練效率提升等…

深度學習入門:神經網絡

文章目錄一、深度學習基礎認知二、神經網絡核心構造解析2.1 神經元的基本原理2.2 感知器&#xff1a;最簡單的神經網絡2.3 多層感知器&#xff1a;引入隱藏層解決非線性問題2.3.1 多層感知器的結構特點2.3.2 偏置節點的作用2.3.3 多層感知器的計算過程三、神經網絡訓練核心方法…

mysql的索引有哪些?

1. 主鍵索引&#xff08;PRIMARY KEY&#xff09;主鍵索引通常在創建表時定義&#xff0c;確保字段唯一且非空&#xff1a;-- 建表時直接定義主鍵 CREATE TABLE users (id INT NOT NULL,name VARCHAR(50),PRIMARY KEY (id) -- 單字段主鍵 );-- 復合主鍵&#xff08;多字段組合…

【計算機視覺與深度學習實戰】08基于DCT、DFT和DWT的圖像變換處理系統設計與實現(有完整代碼python3.13可直接粘貼使用)

1. 引言 數字圖像處理作為計算機視覺和信號處理領域的重要分支,在過去幾十年中得到了快速發展。圖像變換技術作為數字圖像處理的核心技術之一,為圖像壓縮、特征提取、去噪和增強等應用提供了強有力的數學工具。離散余弦變換(Discrete Cosine Transform, DCT)、離散傅里葉變…

使用Python實現DLT645-2007智能電表協議

文章目錄&#x1f334;通訊支持&#x1f334; 功能完成情況服務端架構設計一、核心模塊劃分二、數據層定義三、協議解析層四、通信業務層&#xff08;以DLT645服務端為例&#xff09;五、通信層&#xff08;以TCP為例&#xff09;使用例子&#x1f334;通訊支持 功能狀態TCP客…

未來已來:基于IPv6單棧隔離架構的安全互聯實踐報告

未來已來&#xff1a;基于IPv6單棧隔離架構的安全互聯實踐報告 報告摘要 隨著IPv4地址資源徹底枯竭&#xff0c;全球網絡基礎設施正加速向IPv6單棧&#xff08;IPv6-Only&#xff09;演進。傳統“IPv4為主、IPv6為輔”的雙棧模式已無法滿足數字化轉型對海量地址、端到端連接與原…

Ubuntu24.04 安裝 Zabbix

Ubuntu24.04 安裝 Zabbix 環境&#xff1a; 軟件版本Ubuntu24.04.3Nginx1.24.0MySQL8.4.6PHP8.3.6phpMyAdmin5.2.2Zabbix7.4.1 LNMP 1. 更新本地軟件包索引并升級已安裝軟件 更新可用軟件包列表 把已安裝的軟件升級到最新版 安裝常用工具 sudo apt update && sud…

【動手學深度學習】6.2. 圖像卷積

目錄6.2. 圖像卷積1&#xff09;互相關運算2&#xff09;卷積層3&#xff09;圖像中目標的邊緣檢測4&#xff09;學習卷積核5&#xff09;互相關與卷積6&#xff09;特征映射和感受野7&#xff09;小結. 6.2. 圖像卷積 卷積神經網絡的設計是用于探索圖像數據&#xff0c;本節…

游戲引擎中的Billboard技術

一.視覺公告板為解決場景中Mesh網格面數過多問題,使用2D平面Mesh替換為3D平面Mesh的技術即為Billboard技術.常用于場景中植被,樹葉,粒子系統等對面數有要求的場景.二.Billboard著色器實現著色器輸入參數:攝像機坐標,網格坐標,攝像機觀察方向著色器輸出:實際2D平面隨視角不變