C++单例模式及线程安全(含2004年和2011年版本)

老鸟

一般用 C++ and the Perils of Double-Checked Locking这篇文章,老外写的,加了注释如下:

C++ and the Perils of Double-Checked Locking.pdf

比较沙雕的翻译如下:

C++和双重检查锁定模式(DCLP)的风险.pdf

自从C++11出来后,一切都变了,以前叫人家小甜甜,现在叫牛夫人:

std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load();
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load();
        if (tmp == nullptr) {
            tmp = new Singleton;
            m_instance.store(tmp);
        }
    }
    return tmp;
}

实际上现在是2021年,9年前的东西你应该更加熟悉,那么最简洁的方法就是:


Singleton* Singleton::m_instance;
Singleton* Singleton::getInstance() {
    // 用于call_once的局部静态变量
    static std::once_flag oc;
    std::call_once(oc, [&] {  m_instance = new Singleton();});
    return m_instance;
}

上面例子都是班门弄斧来自:https://blog.csdn.net/10km/article/details/49777749


static创建

有的沙雕说,我直接定义一个静态的返回不就行了嘛?我多聪明啊,而且还有StackOverflow加持:

https://stackoverflow.com/questions/2576022/efficient-thread-safe-singleton-in-c

static single& get_instance()
{
	static single thissingle;
	return thissingle;
}

这个沙雕的做法还是可以的。这里用了一个特性:

全局static是在程序启动的时候初始化,类内static和局部static变量是在第一次运行到的时候初始化。

但是,

如果类构建的时候要带参数,比如日志类,要带个目录,这个static就傻眼了!

并且,据说(大家睁大眼睛,自辩真伪),在局部作用域下的静态变量在编译时,编译器会创建一个附加变量标识静态变量是否被初始化,会被编译器变成像下面这样(伪代码):

static single& get_instance()
{
    static bool is_inited = false;
	static uninitialized single thissingle;
	if(!is_inited){
	    is_inited = true;
	    new (&s) single;
	}
	return thissingle;
}

这里有竞争条件,两个线程同时调用 instance() 时,一个线程运行到 if 语句进入后还没设 is_inited 值,此时切换到另一线程,is_inited值还是 false,同样进入到 if 语句里初始化变量,两个线程都执行了这个单例类的初始化,就不再是单例了。

回收

那还有个问题,这个new出来的东西怎么释放,这个也好办,内部新建一个私有类,然后创建一个静态成员,在析构的时候自动delete:

private:    // 用来回收垃圾,释放内存
    class Release
    {
    public:
        ~Release(){
            if (KrShareMem::m_pInstance != nullptr){
                delete KrShareMem::m_pInstance;
            }
        }
    };
    static Release m_MemRelease;

注意在cpp当作一般静态成员初始化 m_MemRelease 即可。

有好主意的欢迎留言交流

本文为3YL原创,转载无需联系,但请注明来自labisart.com。

原创文章不易,如果觉得有帮助,可打赏或点击右侧广告支持:

查看打赏记录

发表评论请遵守党国法律!后台审核后方可显示!
  • 最新评论
  • 总共2条评论

博主爸爸:你这沙雕真搞笑,我写了几十万行代码都没写过构造函数加参数的单例,都是单例了不会把参数写进类里面么

2021-10-04 12:26:36 回复

  • 3YL 回复 博主爸爸:其实这个沙雕不说是不是写了几十万行,就说后面,哪里说了单例就不能传参数了???你这不是让C++ and the Perils of Double-Checked Locking.pdf这几个兄弟白研究了吗。所以说,经验丰富不可怕,可怕的是目中无人。
  • 2021-10-05 08:22:09 回复

:回收的问题不复杂,我的举例只是为了讲请单例模式没有详细说明,可以借助shared_ptr在程序结束时自动完成资源回收,call_once的例子改进如下:std::shared_ptr<Singleton> Singleton::m_instance;std::shared_ptr<Singleton> Singleton::getInstance() {    // 用于call_once的局部静态变量    static std::once_flag oc;    std::call_once(oc, [&] {  m_instance = std::make_shared<Singleton>();});    return m_instance;}

2021-09-03 10:20:28 回复

  • 3YL 回复 :在h里定义:static std::share_ptr<Singleton> m_instance;我还遇到问题是构造析构函数声明为private不行。使用自定义删除器由于要操作内部成员,又提示非静态调用。
  • 2021-09-28 17:43:25 回复
  • Blog v1.1© 2021 labisart.com 版权所有 | 联系:labartwork@163.com