單例模式與線程池

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/diannao/96628.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96628.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96628.shtml

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

相關文章

Chrome和Edge如何開啟暗黑模式

Edge和Chrome瀏覽器都提供了實驗性功能&#xff0c;可以通過修改實驗性設置來開啟暗黑模式。 在瀏覽器地址欄中輸入edge://flags/&#xff08;Edge&#xff09;或chrome://flags/&#xff08;Chrome&#xff09;。在搜索框中輸入“dark”&#xff0c;找到與暗黑模式相關的選項。…

【科研繪圖系列】浮游植物的溶解性有機碳與初級生產力的關系

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹 數據準備 數據處理 溶解性有機碳(DOC)與初級生產力(NPP)的關系 溶解性有機碳(DOC)與光照強度(PAR)的關系 數據可視化 加載R包 數據下載 導入數據 畫圖1 畫圖2 總結 系統信…

IDEA相關的設置和技巧

IDEA相關的設置和技巧 我的博客對應文章地址 1.布局設置 IDEA的布局自定義程度很高&#xff0c;頂部工具欄&#xff0c;側邊欄都可以隨意定制&#xff0c;設置好的布局方案可以保存&#xff0c;在新項目中快速使用 1.1 工具欄設置 [!tip] 舉個例子&#xff1a;比如我要在頂部…

AWS Lambda 完全指南:解鎖無服務器架構的強大力量

在云計算的發展浪潮中,無服務器(Serverless) 架構已然成為構建現代應用的新范式。而在這場變革的中心,AWS Lambda 作為開創性的 Function-as-a-Service (FaaS) 服務,徹底改變了我們部署和運行代碼的方式。 本文將帶您深入探索 AWS Lambda,從核心概念、工作原理到高級實踐…

人工智能時代下普遍基本收入(UBI)試驗的實踐與探索——以美國硅谷試點為例

一、硅谷UBI試驗的最新進展&#xff08;2025年&#xff09;1. 試驗規模與資金來源圣克拉拉縣試點&#xff1a;硅谷所在地圣克拉拉縣針對脫離寄養家庭的年輕人開展UBI試驗&#xff0c;每月發放1000美元補貼&#xff0c;持續1-2年&#xff0c;覆蓋約60名參與者&#xff0c;成本約…

云計算之云主機Linux是什么?有何配置?如何選?

一、云環境如何選擇Linux發行版 1.1、Linux在各個領域的發展 Linux在各個領域的發展序號Linux發展領域說明1Linux在服務器領域的發展目前Linux在服務器領域已經占據95%的市場份額&#xff0c;同時Linux在服務器市場的迅速崛起&#xff0c;已經引起全球IT產業的高度關注&#xf…

XCVU13P-2FHGB2104E Xilinx(AMD)Virtex UltraScale+ FPGA

XCVU13P-2FHGB2104E 是 Xilinx&#xff08;AMD&#xff09;Virtex UltraScale FPGA 系列中的一款高性能芯片&#xff0c;適用于需要大量邏輯資源、高帶寬和高速數據傳輸的應用場景。作為該系列中的旗艦產品&#xff0c;XCVU13P-2FHGB2104I 結合了強大的處理能力和靈活的可編程性…

自動化單詞例句獲取系統設計方案

方案一 (網絡爬蟲) 這個方案的核心思路是:創建一個自動化的腳本,該腳本會讀取你 MongoDB 中的單詞,然后去一個免費的在線詞典網站上抓取這些單詞的例句,最后將抓取到的例句存回你的 MongoDB 數據庫中對應的單詞條目下。 一、 核心思路與技術選型 自動化腳本: 我們將使用 P…

WPF Alert彈框控件 - 完全使用指南

WPF Alert彈框控件 - 完全使用指南概述快速開始nuget安裝與引用基本用法功能特性詳細說明AlertType 枚舉方法參數詳解Show 方法&#xff08;局部彈窗&#xff09;ShowGlobal 方法&#xff08;全局彈窗&#xff09;完整示例代碼XAML 布局C# 代碼實現界面演示功能特性對比表格自定…

可視化-模塊1-HTML-01

1-軟件下載&#xff1a; 軟件名稱&#xff1a;HBuilderX 官網地址&#xff1a; https://www.dcloud.io/hbuilderx.html 下載文佳-解壓縮-打開exe文件 創建快捷方式至桌面 2-創建項目 【普通項目】-【基本HTML項目】-【項目名&#xff1a;week1-1】 【index】輸入&#xff1…

機器翻譯 (Machine Translation) 經典面試筆試50題(包括詳細答案)

更多內容請見: 機器翻譯修煉-專欄介紹和目錄 文章目錄 第一部分:基礎理論與概念 (1-15題) 1. 題目: 什么是機器翻譯(MT)?請簡述其發展歷程中的幾個主要范式。 2. 題目: 機器翻譯的主要評價指標有哪些?請詳細解釋BLEU指標的計算原理和優缺點。 3. 題目: 什么是平行語料…

linux中文本文件操作之grep命令

文章目錄背景案例demo環境方式一、安裝wsl方式二、安裝grep一、查找指定字符串二、忽略大小寫查找三、查找時顯示行號四、統計匹配的次數五、精準匹配一個單詞六、顯示匹配上下文七、只顯示匹配的內容八、按固定字符串匹配背景 在日常運維中會對日志文件&#xff0c;使用grep命…

鏈表漫游指南:C++ 指針操作的藝術與實踐

文章目錄0. 前言1. 鏈表的分類2. 單鏈表的實現2.1 鏈表的基本結構——節點&#xff08;Node&#xff09;2.2 核心操作詳解2.2.1 構造和析構2.2.2 插入操作2.2.3 刪除操作2.3.4 其他操作2.4 總結3. 雙向鏈表的實現3.1 基本結構設計3.2 基本操作3.2.1 初始化與銷毀3.2.2 插入與刪…

Claude Code賦能企業級開發:外賣平臺核心系統的智能化重構

開篇&#xff1a;萬億市場背后的技術挑戰中國外賣市場日訂單量超過1億單&#xff0c;每一單背后都是一個復雜的技術鏈條&#xff1a;用戶下單→商家接單→騎手搶單→實時配送→評價反饋。構建這樣一個支撐千萬級并發、涉及地理位置計算、實時調度、支付結算的超級平臺&#xff…

【使用Unsloth 微調】數據集的種類

1. 什么是數據集 對于大型語言模型&#xff08;LLMs&#xff09;&#xff0c;數據集是用于訓練模型的數據集合。為了訓練有效&#xff0c;文本數據需要能夠被分詞&#xff08;tokenized&#xff09;。創建數據集的關鍵部分之一是聊天模板&#xff08;chat template&#xff09;…

【碼蹄杯】2025年本科組省賽第一場

個人主頁&#xff1a;Guiat 歸屬專欄&#xff1a;算法競賽 文章目錄1. MC0455 四大名著-西游簽到2. MC0456 斬斷靈藤3. MC0457 符咒封印4. MC0458 移鐵術5. MC0459 昆侖墟6. MC0460 星空迷軌陣7. MC0461 排隊8. MC0462 最后一難正文 總共8道題。 1. MC0455 四大名著-西…

CentOS 10安裝Ollama

前置說明 linux服務器版本&#xff1a;CentOS10 ollama版本&#xff1a;v0.11.6 下載安裝包 下載安裝包 官網地址&#xff1a;Ollama 下載地址&#xff1a;Download Ollama 選擇linux平臺&#xff0c;由于使用官網提供的腳本直接安裝容易失敗&#xff0c;這里選擇手動下…

手機、電腦屏幕的顯示壞點檢測和成像原理

如今&#xff0c;手機和電腦屏幕已成為人們日常生活和工作中不可或缺的一部分。無論是處理文檔、觀看視頻&#xff0c;還是進行專業設計&#xff0c;屏幕的顯示質量都直接影響著用戶體驗。本文將介紹屏幕顯示的基本原理&#xff0c;包括RGB色素構成和成像機制&#xff0c;并進一…

文件與fd

文件與fd一、前置預備二、復習c語言文件三、系統文件認識3.1 系統層面有關文件的接口&#xff08;open&#xff09;&#xff1a;![在這里插入圖片描述](https://i-blog.csdnimg.cn/direct/b15577967d1445b08cd5252f2009683a.png)3.2 簡單使用open參數3.3 語言vs系統3.4 進一步理…

語義通信高斯信道仿真代碼

1?? 代碼 def AWGN(coding, snr, devicecpu):"""為輸入張量添加高斯白噪聲&#xff08;AWGN&#xff09;&#xff0c;根據指定的 SNR&#xff08;分貝&#xff09;控制噪聲強度。參數&#xff1a;coding (torch.Tensor): 輸入張量&#xff0c;形狀為 [batch_s…