您的位置:首页 > 其它

设计模式之单例模式 (模板 智能指针 删除器) 实现【懒汉】

2016-03-29 15:05 489 查看
上一篇关于单例模式的实现 主要是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 进行调试。

欢迎大家批评指正~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: