Singleton這個題目酸燃被出到爛了,不過變化實在千變萬化。列出幾種不錯的解決方式。
Meyers version
1 | class Singleton { |
非常有名的實作方式,在C++11的環境下是Thread-safe的,而C++98沒有這種保證。可以參考Is Meyers implementation of Singleton pattern thread safe?這個討論串。
GCC預設編譯時開啟static threadsafe的選項,所以C++11的程式碼可以正確運行,可以強制使用-fno-threadsafe-statics
關閉這功能。可以參考Are function static variables thread-safe in GCC?
call_once and once_flag
C++11新增的call_once功能可以保證函數只執行一次,因此可以拿來用在Singleton的建立上。libuve也有類似的thread-safe callonce功能。C語言如果有需要的話可以從那邊找答案。
1 |
|
自行打造輪子的作法
為了練功,有時自己倒造輪子可以有更深一曾的了解
Thread-safe lock version
1 |
|
這個版本很簡單,在每個Thread呼叫getInstance時就獲取Lock,因此可以保證Constructor只會有一個Thread執行,不過這方式的缺點也很明顯,當建立完singleton之後就不需Lock了。
Double-Checked Locking
最受爭議的實作方式,原因不是在於程式碼有問題,而是Compiler & Cpu不會照程式碼所預料的方式行動。最原始寫法是這樣
1 | Singleton& Singleton::instance() { |
這個問題的原因很複雜,可以看這兩篇文章
- The “Double-Checked Locking is Broken” Declaration
- C++ and the Perils of Double-Checked Locking
- Double-Checked Locking, Threads, Compiler Optimizations, and More
根據論文最後,可以看到一個可行的解需要Memory barrier的部份,由於C++98對此沒有規範,所以只能靠Compiler個顯神吞。1
2
3
4
5
6
7
8
9
10
11
12
13
14Singleton* Singleton::instance() {
Singleton *tmp = pInstance;
... // need memory barrier
if (tmp == 0) { // 1st test
Lock lock;
tmp = pInstance;
if(tmp == 0) { // 2nd test
tmp = new Singleton;
... // need memory barrier
pInstance = tmp;
}
}
return pInstance;
}Double-Checked Locking revisited
在C++11之後,引進了atomic操作。因此上面的方式變得可能。
實際寫了一下,目前的atomic跟unique_ptr不能共用,等待N4058 - Atomic Smart Pointers哪一天能通過。現在先用Raw pointer來做。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Singleton {
private:
static std::atomic<Singleton *> instance;
static std::mutex instanceMutex;
Singleton() {}
public:
static Singleton& getInstance() {
Singleton* tmp = instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(instanceMutex);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);
instance.store(tmp, std::memory_order_relaxed);
}
}
return *tmp;
}
};
std::mutex Singleton::instanceMutex;
std::atomic<Singleton *> Singleton::instance;
以下這幾種方式是從 Binary Hacks學來的
Thread-local storage based solution
雖然這跟Singleton的定義不太相同,每個Thread擁有自己的Singleton,如此之來就不用加任何Lock了,如果對此計較的話不是用。
1 | class Singleton { |
Self Modification Code
運用Self Modification Code這種高等技巧,在第一次執行完getInstance()之後,就把整個程式碼寫成類似這樣的情形,不過這需要對Target Architecture相當的熟悉,又不能跨平台,只能當種炫技。不過SMC的技術有時間可以練練。
1 | class Singleton { |
Reference
- Double-Checked Locking Is Fixed In C++11
- Binary Hacks:駭客秘傳技巧一百招