C++Singleton的DCLP(双重锁)实现以及性能测评
2019獨角獸企業重金招聘Python工程師標準>>>
?
本文系原創,轉載請注明:http://www.cnblogs.com/inevermore/p/4014577.html
?
根據維基百科,對單例模式的描述是:
確保一個類只有一個實例,并提供對該實例的全局訪問。
從這段話,我們可以得出單例模式的最重要特點:
一個類最多只有一個對象
?
單線程環境
?
對于一個普通的類,我們可以任意的生成對象,所以我們為了避免生成太多的類,需要將類的構造函數設置為私有。
所以我們寫出第一步:
class Singleton { public:private:Singleton() { } };此時在main中就無法直接生成對象:
Singleton s; //ERROR那么我們想要獲取實例,只能借助于類內部的函數,于是我們添加一個內部的函數,而且必須是static函數(思考為什么):
class Singleton { public:static Singleton *getInstance(){return new Singleton;} private:Singleton() { } }; OK,我們可以用這個函數生成對象了,但是每次都去new,無法保證唯一性,于是我們將對象保存在一個static指針內,然后每次獲取對象時,先檢查該指針是否為空: class Singleton { public:static Singleton *getInstance(){if(pInstance_ == NULL) //線程的切換 {::sleep(1);pInstance_ = new Singleton;}return pInstance_;} private:Singleton() { }static Singleton *pInstance_; };Singleton *Singleton::pInstance_ = NULL;我們在main中測試:
cout << Singleton::getInstance() << endl; cout << Singleton::getInstance() << endl;可以看到生成了相同的對象,單例模式編寫初步成功。
?
多線程環境下的考慮
?
但是目前的代碼就真的沒問題了嗎?
我寫出了以下的測試:
class TestThread : public Thread { public:void run(){cout << Singleton::getInstance() << endl;cout << Singleton::getInstance() << endl;} };int main(int argc, char const *argv[]) {//測試證明了多線程下本代碼存在競爭問題 TestThread threads[12];for(int ix = 0; ix != 12; ++ix){threads[ix].start();}for(int ix = 0; ix != 12; ++ix){threads[ix].join();}return 0; }?
這里注意,為了達到效果,我特意做了如下改動:
if(pInstance_ == NULL) //線程的切換 {::sleep(1);pInstance_ = new Singleton; }這樣故意造成線程的切換。
打印結果如下:
0xb1300468 0xb1300498 0x9f88728 0xb1300498 0xb1300478 0xb1300498 0xb1100488 0xb1300498 0xb1300488 0xb1300498 0xb1300498 0xb1300498 0x9f88738 0xb1300498 0x9f88748 0xb1300498 0xb1100478 0xb1300498 0xb1100498 0xb1300498 0xb1100468 0xb1300498 0xb11004a8 0xb11004a8?
很顯然,我們的代碼在多線程下經不起推敲。
怎么辦?加鎖! 于是我們再度改進:
class Singleton { public:static Singleton *getInstance(){mutex_.lock();if(pInstance_ == NULL) //線程的切換pInstance_ = new Singleton;mutex_.unlock();return pInstance_;} private:Singleton() { }static Singleton *pInstance_;static MutexLock mutex_; };Singleton *Singleton::pInstance_ = NULL; MutexLock Singleton::mutex_;此時測試,無問題。
但是,互斥鎖會極大的降低系統的并發能力,因為每次調用都要加鎖,等于一群人過獨木橋。
我寫了一份測試如下:
class TestThread : public Thread { public:void run(){const int kCount = 1000 * 1000;for(int ix = 0; ix != kCount; ++ix){Singleton::getInstance();}} };int main(int argc, char const *argv[]) {//Singleton s; ERROR int64_t startTime = getUTime();const int KSize = 100;TestThread threads[KSize];for(int ix = 0; ix != KSize; ++ix){threads[ix].start();}for(int ix = 0; ix != KSize; ++ix){threads[ix].join();}int64_t endTime = getUTime();int64_t diffTime = endTime - startTime;cout << "cost : " << diffTime / 1000 << " ms" << endl;return 0; }開了100個線程,每個調用1M次getInstance,其中getUtime的定義如下:
int64_t getUTime() {struct timeval tv;::memset(&tv, 0, sizeof tv);if(gettimeofday(&tv, NULL) == -1){perror("gettimeofday");exit(EXIT_FAILURE);}int64_t current = tv.tv_usec;current += tv.tv_sec * 1000 * 1000;return current; }運行結果為:
cost : 6914 ms?
?
采用雙重鎖模式
?
上面的測試,我們還無法看出性能問題,我再次改進代碼:
class Singleton { public:static Singleton *getInstance(){if(pInstance_ == NULL){mutex_.lock();if(pInstance_ == NULL) //線程的切換pInstance_ = new Singleton;mutex_.unlock();}return pInstance_;} private:Singleton() { }static Singleton *pInstance_;static MutexLock mutex_; };Singleton *Singleton::pInstance_ = NULL; MutexLock Singleton::mutex_;可以看到,我在getInstance中采用了兩重檢查模式,這段代碼的優點體現在哪里?
內部采用互斥鎖,代碼無論如何是可靠的
new出第一個實例后,后面每個線程訪問到最外面的if判斷就直接返回了,沒有加鎖的開銷
我再次運行測試,(測試代碼不變),結果如下:
cost : 438 ms啊哈,十幾倍的性能差距,可見我們的改進是有效的,僅僅三行代碼,卻帶來了十幾倍的效率提升!
?
尾聲
?
上面這種編寫方式成為DCLP(Double-Check-Locking-Pattern)模式,這種方式一度被認為是絕對正確的,但是后來有人指出這種方式在某些情況下也會亂序執行,可以參考Scott的C++ and the Perils of Double-Checked Locking - Scott Meyer
轉載于:https://my.oschina.net/inevermore/blog/388676
總結
以上是生活随笔為你收集整理的C++Singleton的DCLP(双重锁)实现以及性能测评的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql主从双向同步复制
- 下一篇: s3c2440移植MQTT