網站首頁 編程語言 正文
單例模式的簡單實現
單例模式大概是流傳最為廣泛的設計模式之一了。一份簡單的實現代碼大概是下面這個樣子的:
class singleton
{
public:
static singleton* instance()
{
if (inst_ != nullptr) {
inst_ = new singleton();
}
return inst_;
}
private:
singleton(){}
static singleton* inst_;
};
singleton* singleton::inst_ = nullptr;
這份代碼在單線程的環境下是完全沒有問題的,但到了多線程的世界里,情況就有一點不同了。考慮以下執行順序:
- 線程1執行完if (inst_ != nullptr)之后,掛起了;
- 線程2執行instance函數:由于inst_還未被賦值,程序會inst_ = new singleton()語句;
- 線程1恢復,inst_ = new singleton()語句再次被執行,單例句柄被多次創建。
所以,這樣的實現是線程不安全的。
有問題的雙重檢測鎖
解決多線程的問題,最常用的方法就是加鎖唄。于是很容易就可以得到以下的實現版本:
class singleton
{
public:
static singleton* instance()
{
guard<mutex> lock{ mut_ };
if (inst_ != nullptr) {
inst_ = new singleton();
}
return inst_;
}
private:
singleton(){}
static singleton* inst_;
static mutex mut_;
};
singleton* singleton::inst_ = nullptr;
mutex singleton::mut_;
這樣問題是解決了,但性能上就不那么另人滿意,畢竟每一次使用instance都多了一次加鎖和解鎖的開銷。更關鍵的是,這個鎖也不是每次都需要啊!實際我們只有在創建單例實例的時候才需要加鎖,之后使用的時候是完全不需要鎖的。于是,有人提出了一種雙重檢測鎖的寫法:
...
static singleton* instance()
{
if (inst_ != nullptr) {
guard<mutex> lock{ mut_ };
if (inst_ != nullptr) {
inst_ = new singleton();
}
}
return inst_;
}
...
我們先判斷一下inst_是否已經初始化了,如果沒有,再進行加鎖初始化流程。這樣,雖然代碼看上去有點怪異,但好像確實達到了只在創建單例時才引入鎖開銷的目的。不過遺憾的是,這個方法是有問題的。Scott Meyers 和 Andrei Alexandrescu 兩位大神在C++ and the Perils of Double-Checked Locking?一文中對這個問題進行了非常詳細地討論,我們在這兒只作一個簡單的說明,問題出在:
inst_ = new singleton();
這一行。這句代碼不是原子的,它通常分為以下三步:
- 調用operator new為singleton對象分配內存空間;
- 在分配好的內存空間上調用singleton的構造函數;
- 將分配的內存空間地址賦值給inst_。
如果程序能嚴格按照1-->2-->3的步驟執行代碼,那么上述方法沒有問題,但實際情況并非如此。編譯器對指令的優化重排、CPU指令的亂序執行(具體示例可參考《【多線程那些事兒】多線程的執行順序如你預期嗎?》)都有可能使步驟3執行早于步驟2。考慮以下的執行順序:
- 線程1按步驟1-->3-->2的順序執行,且在執行完步驟1,3之后被掛起了;
- 線程2執行instance函數獲取單例句柄,進行進一步操作。
由于inst_在線程1中已經被賦值,所以在線程2中可以獲取到一個非空的inst_實例,并繼續進行操作。但實際上單例對像的創建還沒有完成,此時進行任何的操作都是未定義的。
現代C++中的解決方法
在現代C++中,我們可以通過以下幾種方法來實現一個即線程安全、又高效的單例模式。
使用現代C++中的內存順序限制
現代C++規定了6種內存執行順序。合理的利用內存順序限制,即可避免代碼指令重排。一個可行的實現如下:
class singleton {
public:
static singleton* instance()
{
singleton* ptr = inst_.load(memory_order_acquire);
if (ptr == nullptr) {
lock_guard<mutex> lock{ mut_ };
ptr = inst_.load(memory_order_relaxed);
if (ptr == nullptr) {
ptr = new singleton();
inst_.store(ptr, memory_order_release);
}
}
return inst_;
}
private:
singleton(){};
static mutex mut_;
static atomic<singleton*> inst_;
};
mutex singleton::mut_;
atomic<singleton*> singleton::inst_;
來看一下匯編代碼:
可以看到,編譯器幫我們插入了必要的語句來保證指令的執行順序。
使用現代C++中的call_once方法
call_once也是現代C++中引入的新特性,它可以保證某個函數只被執行一次。使用call_once的代碼實現如下:
class singleton
{
public:
static singleton* instance()
{
if (inst_ != nullptr) {
call_once(flag_, create_instance);
}
return inst_;
}
private:
singleton(){}
static void create_instance()
{
inst_ = new singleton();
}
static singleton* inst_;
static once_flag flag_;
};
singleton* singleton::inst_ = nullptr;
once_flag singleton::flag_;
來看一下匯編代碼:
可以看到,程序最終調用了__gthrw_pthread_once來保證函數只被執行一次。
使用靜態局部變量
現在C++對變量的初始化順序有如下規定:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
所以我們可以簡單的使用一個靜態局部變量來實現線程安全的單例模式:
class singleton
{
public:
static singleton* instance()
{
static singleton inst_;
return &inst_;
}
private:
singleton(){}
};
來看一下匯編代碼:
可以看到,編譯器已經自動幫我們插入了相關的代碼,來保證靜態局部變量初始化的多線程安全性。
原文鏈接:https://www.cnblogs.com/lc19890709/p/16809373.html
相關推薦
- 2022-08-19 Exception evaluating SpringEL expression異常處理
- 2022-04-24 Android掛斷電話最新實現方法_Android
- 2023-10-27 解決webpack打包后圖片加載失敗的bug(適用于所有本地靜態資源)
- 2023-03-01 PostgreSQL生成列實現過程介紹_PostgreSQL
- 2022-02-18 Zabbix Database error
- 2022-07-09 基于fluttertoast實現封裝彈框提示工具類_Android
- 2023-07-02 Python?中的裝飾器實現函數的緩存(場景分析)_python
- 2022-12-11 瀏覽器控制臺報錯Failed?to?load?module?script:解決方法_nginx
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支