上一篇關於單例模式的實現 主要是Doubke-Check Locking。
但是多線程環境下並不能保證完全線程安全。
這篇文章實現的單例:
懶漢模式 的單例
基於模板和繼承
線程安全
使用智能指針 防止內存泄露
效率相對較高
實現代碼如下:
#include <iostream>
#include <memory>
#include <windows.h>
#include <process.h>
using namespace std;
template <class T>
class CSingletonPtr
{
// 私有刪除器
private:
class Deleter
{
public:
// 重載()操作符
void operator()(T* p)
{
delete p;
}
};
private:
static tr1::shared_ptr<T> m_pInstance; // 智能指針 避免內存泄露
private:
CSingletonPtr(const CSingletonPtr&){}
CSingletonPtr& operator=(const CSingletonPtr&){}
protected:
// 需要繼承 所以一定要聲明爲public
CSingletonPtr()
{
cout << "CSingletonPtr begin construct" << endl;
::Sleep(1000);
cout << "CSingletonPtr end construct" << endl;
}
virtual ~CSingletonPtr()
{
cout << "CSingletonPtr destrcut" << endl;
}
public:
// 返回引用 避免拷貝構造和析構 提高效率
static T& getInstance()
{
// static 表明變量是跨線程共享的
// volatile 表示不進行編譯器優化
static volatile long lock = 0;
/*
if (lock == 0)
{
lock = 1;
}
分析上面這段代碼, 考慮下面這種情況:
線程1正在執行 if(lock == 0)的判斷
此時切換到線程2 線程2正在執行lock = 1;
因此會出問題 這種情況需要避免。
使用InterlockedCompareExchange函數解決上面出現的問題
一個線程調用該函數 那麼會立即鎖定變量內存地址 其他線程不可同時訪問
注:只能鎖定32位(4字節)變量
*/
int res = 0;
// 只有第一次調用InterlockedCompareExchange的線程纔會跳過下面的if塊 創建單例的實例 並返回
// 使用編譯器提供的本質函數 _InterlockedCompareExchange 提高效率
if (res = ::_InterlockedCompareExchange(&lock, 1, 0) != 0)
{
cout << res << "---" << " Thread ID = " << ::GetCurrentThreadId() << endl;
// 不是第一次調用該函數的線程 會一直輪詢等待
// 直至第一次調用該函數的線程創建好單例並返回單例實例 纔會結束輪詢
while (lock != 2)
{
::Sleep(0);
}
return *m_pInstance.get();
}
tr1::shared_ptr<T> tmp(new T(), T::Deleter());
m_pInstance = tmp;
lock = 2; // 內存分配好了就讓其他線程停止等待
cout << "內存分配初始化完成" << endl;
return *m_pInstance.get();
}
};
// 模板靜態成員初始化***
template <class T>
tr1::shared_ptr<T> CSingletonPtr<T>::m_pInstance = NULL;
// 實例化模板
class Manager : public CSingletonPtr<Manager>
{
// 訪問權限授權給基類
friend class CSingletonPtr<Manager>;
protected:
Manager()
{
cout << "Manager begin construct" << endl;
::Sleep(500);
m_count = 0;
cout << "Manager end construct" << endl;
}
~Manager()
{
cout << "Manager destrcut" << endl;
}
public:
void print()
{
cout << "Hello, this is Manager " << m_count++ << endl;
}
private:
int m_count;
};
//-----------------多線程測試-----------------
unsigned int __stdcall threadFun(void*)
{
cout << "Current Thread ID = " << ::GetCurrentThreadId() << endl;
Manager::getInstance().print();
return 0;
}
void testMultiThread()
{
for (int i = 0; i < 3; ++i)
{
uintptr_t th = ::_beginthreadex(NULL, 0, threadFun, NULL, 0, NULL);
::CloseHandle((HANDLE)th);
}
}
int main(void)
{
testMultiThread();
getchar();
return 0;
}
貼一下運行結果圖:
注意到代碼中針對原子鎖的使用的代碼。
第一次調用res = 0 不會進入if語句塊 不會打印 "res --- Thread ID = "這句話
線程切換後 會打印那句話 我們可以看到 剩餘的兩個線程都打印了。
這正好驗證了我們的思路。
第一個線程創建單例返回後 其餘線程纔會退出等待。
另外 內存分配完成後,調用getInstance()獲取單例。
然後調用print()打印 "Hello, this is Manager"那句話
多次運行結果可能不太一樣。
因爲 內存分配完成後
CPU的所有權現在不確定在哪個線程上,獲取到線程都可以去執行print()函數。
這時候不存在線程等待和輪詢的問題。
可以在print()方法打印語句中 輸出當前線程ID 進行調試。
歡迎大家批評指正~