Q_GLOBAL_STATIC用法及如何保证多线程下的单例模式安全性

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;
    }

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值