設計模式之單例模式 (模板 智能指針 刪除器) 實現【懶漢】

上一篇關於單例模式的實現 主要是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 進行調試。


歡迎大家批評指正~


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章