1.Q_GLOBAL_STATIC用法
Q_GLOBAL_STATIC的作用,如果英文水平可以的话,请参见Qt官方的qt assist;如果英文水平不咋地,请参见下面博文:
《qt -- Q_GLOBAL_STATIC创建全局静态对象》
2.单例模式
很多人洋洋洒洒写了一大堆
比如这里 http://xtuer.github.io/qtbook-singleton/
比如这里 http://m.blog.csdn.net/Fei_Liu/article/details/69218935
3.普通单例模式存在的问题
一般人会写出类似如下的单例模式的代码:
CSingleton.h:
#pragma once
class CSingleton
{
public:
static CSingleton* getSingletonInstance();
void test();
private:
/* 构造函数设置为私有的,以便外层调用方不能直接构造
* ,防止构造多个本类的对象*/
CSingleton();
/*析构函数定义为 private 的,是为了防止其他地方使用
delete 删除 CSingleton 的对象*/
~CSingleton();
/*拷贝构造函数、移动构造函数、赋值操作符定义为 private且是删除的,
是为了防止通过这几个函数创建新的本类对象
* */
CSingleton(const CSingleton& s) = delete;
CSingleton(const CSingleton&& s) = delete;
CSingleton& operator = (const CSingleton& s) = delete;
private:
static CSingleton* m_pSingleton;
};
CSingleton.cpp:
#include "singleton.h"
#include<QDebug>
CSingleton* CSingleton::m_pSingleton = nullptr;
CSingleton::CSingleton()
{
}
CSingleton::~CSingleton()
{
delete m_pSingleton;
}
CSingleton* CSingleton::getSingletonInstance()
{
if (nullptr == m_pSingleton)
{
m_pSingleton = new CSingleton();
}
return m_pSingleton;
}
void CSingleton::test()
{
qDebug() << "hello \r\n";
}
上述代码在非多线程环境下是没有问题的;但在多线程环境下,由于线程之间存在资源竞争,会导致CSingleton.cpp第12、18行存在竞争,也就是第12行会delete多次,第18行会存在多个CSingleton类的对象,从而导致单例失败、内存泄漏。很多人想到的解决方法是为线程加锁,即改为类似如下:
CSingleton.h文件如下:
class CSingleton
{
..........................// 这里还是保持和以前的一样
/**
* 这里加入delete唯一对象的函数
*/
static void release();
private:
// 这里声明一个锁,用于多线程互斥
static QMutex m_mutex;
}
CSingleton.cpp中的getSingletonInstance函数改为如下:
CSingleton* CSingleton::getSingletonInstance()
{
if (nullptr == m_pSingleton)
{
m_mutex.lock();
if (nullptr == m_pSingleton)
{
m_pSingleton= new CSingleton();
}
m_mutex.unlock();
}
return m_pSingleton;
}
CSingleton.cpp中加入如下release函数:
void CSingleton::release()
{
if (nullptr != m_pSingleton)
{
m_mutex.lock();
delete m_pSingleton;
m_pSingleton= 0;
m_mutex.unlock();
}
}
在CSingleton类的析构函数中调用release函数:
CSingleton::~CSingleton()
{
release();
}
双重锁定单例模式在实践中可以发现,会因为指令编排方式不同,导致一些异常情况发生,因为线程A中m_pSingleton分配了指针,CSingleton的构造函数还没有执行,但此时线程B开始调用getSingletonInstance()接口,因为m_pSingleton已经非空,直接执行CSingleton::getSingletonInstance() ->test(),就导致异常情况发生了。且每次调用 CSingleton::getSingletonInstance() 的时候都要加锁,效率是很低的。
3.1 利用 Qt中的Q_GLOBAL_STATIC解决多线程单例安全问题
Qt中的Q_GLOBAL_STATIC宏正是为解决此问题而提出的。用Q_GLOBAL_STATIC重写上面的代码如下:
#pragma once
class CSingleton
{
public:
CSingleton();
~CSingleton();
public:
void test();
public:
static CSingleton* getSingletonInstance();
};
#include "singleton.h"
#include<QGlobalStatic>
#include<QDebug>
Q_GLOBAL_STATIC(CSingleton, m_pSingleton)
CSingleton::CSingleton()
{
}
CSingleton::~CSingleton()
{
}
CSingleton* CSingleton::getInstance()
{
return m_pSingleton;
}
void CSingleton::test()
{
qDebug() << "hello \r\n";
}
由于Q_GLOBAL_STATIC是线程安全的,所以不用担心加锁、解锁、及线程竞争的问题,且效率相比锁机制高、代码更简洁。对于调用方来说,调用都是一样的,如:
CSingleton::getInstance()->test();
3.2 利用C++11的std::call_once解决多线程单例安全问题
对于上面的多线程单例安全问题,也可以用C++11的std::call_once解决。为了解决双重锁定以及创建多个实例的问题,C++11标准库提供了std::once_flag和std::call_once来处理只初始化一次的情况。使用std::call_once比显式使用互斥量消耗的资源更少,并且还是无锁式编程,减少了死锁问题的发生。
call_once是c++11中引入的新特性,用于保证某个函数只调用一次,即使是多线程环境下,它也可以可靠地完成一次函数调用。特别适用于某个初始化只执行一次的场景。
若调用call_once一切顺利,将会翻转once_flag变量的内部状态,再次调用该函数时,所对应的目标函数不会被执行。
若调用call_once中发生异常,不会翻转once_flag变量的内部状态,再次调用该函数时,目标函数仍然尝试执行。
下面代码是在win10+vs2017编译器测试通过,演示了如何使用c++11 中的call_once方法
#include "stdafx.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
//单利模式应用
class CSinglton
{
private:
//(1)私有额构造函数
CSinglton() {}
//在析构函数中释放实例对象
~CSinglton()
{
if (pInstance != NULL)
{
delete pInstance;
pInstance = NULL;
}
}
/*拷贝构造函数、移动构造函数、赋值操作符定义为 private且的删除的,
是为了防止通过创建新的本类对象
* */
CSinglton(const CSinglton& s) = delete;
CSinglton(const CSinglton&& s) = delete;
CSinglton& operator = (const CSinglton& s) = delete;
public:
//(3)获得本类实例的唯一全局访问点
static CSinglton* GetInstance()
{
//若实例不存在,则尝试创建实例对象
if (NULL == pInstance)
{
//call_once object makes sure calling CreateInstance function only one time;
//it will be safe without lock;
try
{
std::call_once(m_flag, CreateInstance);
}
catch (...)
{
std::cout << "CreateInstance error\n";
}
}
//实例已经存在,直接该实例对象
return pInstance;
}
static void CreateInstance()
{
pInstance = new(std::nothrow) CSinglton();//分配失败,是返回NULL;
if (NULL == pInstance)
{
throw std::exception();
}
}
private:
static CSinglton* pInstance;//(2)唯一实例对象
static std::once_flag m_flag;
};
CSinglton* CSinglton::pInstance = NULL;
//构造 once_flag 对象,内部状态被设为指示函数仍未被调用。
std::once_flag CSinglton::m_flag;
//辅助测试代码
std::mutex g_mutex;
void PrintInstanceAddr()
{
std::this_thread::sleep_for(std::chrono::microseconds(1));
//get instance
CSinglton* pIns = CSinglton::GetInstance();
//print instance addr
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << pIns << std::endl;
}
int main()
{
std::thread td[5];
//multhread get instance addr;
for (int i = 0; i < 5; i++)
{
td[i] = std::thread(PrintInstanceAddr);
}
for (int i = 0; i < 5; i++)
{
td[i].join();
}
return 0;
}
运行结果:
0076E778
0076E778
0076E778
0076E778
0076E778
注意:上面的单例模式即直接按下面那样不加锁,不用std::call_once调用,在多线程中会因为线程竞争而导致不正确的结果出现。
//(3)获得本类实例的唯一全局访问点
static CSinglton* GetInstance()
{
//若实例不存在,则尝试创建实例对象
if (NULL == pInstance)
{
try
{
CreateInstance);
}
catch (...)
{
std::cout << "CreateInstance error\n";
}
}
//实例已经存在,直接该实例对象
return pInstance;
}