1.单例类
某个类只能有一个对象,即为单例
class A
{
}
...
A a1;
A a2;//这就不是单例,不能有第二个对象
下面是一个常用的C++单例类的形式:
class S
{
private:
S() {} //私有构造函数,单例类不能通过普通放方式构造对象
private:
static S* m_Instance; //指向该类唯一的对象的指针
public:
static S* GetInstance() {//注意这是个静态成员函数
if (m_Instance == NULL) {//如果没有对象,则构造对象
m_Instance = new S;
static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
}
return m_Instance;
}
class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
{
public:
~ToDestroy(){
if (S::m_Instance){
delete S::m_Instance;
S::m_Instance = NULL;
}
}
};
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!
这个类的特点:
1.构造函数是private的
2.有一个静态指针,即该类唯一的对象的地址,同样是private
3.使用静态GetInstance接口来获取唯一对象的地址,若地址为空则创建对象并返回地址,若地址非空则直接返回地址
4.这里使用一个嵌套类来释放资源,可有可无
5.记得要为类的静态成员变量赋值!!!
2.单例类在多线程中的问题
1.以上单例类创建对象的过程一定要在出现多线程之前完成
先把单例类对象创建好,里面的数据都设计好,在后面分多线程的时候只读数据就不会有问题
2.如果一定要在分出多线程后再去调用GetInstance()创建单例类的对象就会出现问题。
我们需要加一个锁来解决这个问题
include<thread>//C++11
include<mutex>
mutex _Mutex;
class S
{
private:
S() {} //私有构造函数,单例类不能通过普通放方式构造对象
private:
static S* m_Instance; //指向该类唯一的对象的指针
public:
static S* GetInstance() {//注意这是个静态成员函数
if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
lock_guard<mutex> guard(_Mutex);//加锁,自动释放
if (m_Instance == NULL) {
m_Instance = new S;
static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
}
}
return m_Instance;
}
class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
{
public:
~ToDestroy(){
if (S::m_Instance){
delete S::m_Instance;
S::m_Instance = NULL;
}
}
};
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!
int main()
{
auto fun = [] {
Single::GetInstance()->Run();
};
thread tobj1(fun);
thread tobj2(fun);
tobj1.join();
tobj2.join();
return 0;
}
这个类的特点:
1.mutex一定不能是类的成员
2.使用双重锁定提高效率
3.互斥量加锁的位置是在第二个判断m_Intance是否存在的前面
4.这里使用一个嵌套类来释放资源,可有可无
那么为什么不能这样写:
if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
lock_guard<mutex> guard(_Mutex);//加锁,自动释放
m_Instance = new S;
static ToDestroy destroyer;
}
即不要第二个判断。要注意,第一次运行时,如果在
if (m_Instance == NULL)
后切换了上下文,则多个线程都进入到了判断体中,一个线程锁住先进行了new语句,解锁后其他进入判断体内的线程都会在挨个new一次,这显然是不对的。
所以,在锁上之后,一定得判断一下。
而在所之前也得判断一下,否则每次调用都得锁。
3.使用call_once()进一步加速
call_once()的第二个参数是一个函数名fun
call_once()的功能是可以保证这个函数fun()只会被调用一次
call_once()具有互斥量的功能,但消耗的资源更少。
call_once()需要与标记std::once_flag结合使用。
#include<iostream>
#include<thread>
#include <deque>
#include<mutex>
#include <windows.h>
mutex mMutex;
std::once_flag mFlag;
class Single
{
private:
Single() {} //私有构造函数,单例类不能通过普通放方式构造对象
static Single* m_Instance; //指向该类唯一的对象的指针
static void CreateInstance() {
m_Instance = new Single;
static ToDestroy destroyer;
}
public:
static Single* GetInstance() {
call_once(mFlag, CreateInstance);//28985ms
//if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
// lock_guard<mutex> guard(mMutex);//加锁,自动释放
// if (m_Instance == NULL) {
// m_Instance = new Single;
// static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
// }
//}//32203ms
return m_Instance;
}
class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
{
public:
~ToDestroy(){
if (Single::m_Instance){
delete Single::m_Instance;
Single::m_Instance = NULL;
}
}
};
void Run() { cout << "good" << endl; }
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!
int main()
{
auto fun = [] {
for (int i = 0; i < 10000; i++)
Single::GetInstance()->Run();
};
DWORD timeSpent = 0;
DWORD beginTime = GetTickCount();
/*thread tobj1(fun);
thread tobj2(fun);
tobj1.join();
tobj2.join();*/
for (int i = 0; i < 100000; i++)
Single::GetInstance()->Run();
DWORD endTime = GetTickCount();
timeSpent = endTime - beginTime;
cout << "耗时:" << timeSpent << "ms" << endl;
system("pause");
return 0;
}
在release x86环境中测试发现
使用
call_once(mFlag, CreateInstance);
耗时28985ms
而使用
if (m_Instance == NULL) {
lock_guard<mutex> guard(m_Mutex);
if (m_Instance == NULL) {
m_Instance = new Single;
static ToDestroy destroyer;
}
}
则需要32203ms