服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - 从C++单例模式到线程安全详解

从C++单例模式到线程安全详解

2021-04-22 15:06C++教程网 C/C++

下面小编就为大家带来一篇从C++单例模式到线程安全详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

先看一个最简单的教科书式单例模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CSingleton
{
public:
    static CSingleton* getInstance()
    {
        if (NULL == ps)
        {//tag1
            ps = new CSingleton;
        }
        return ps;
    }
 
private:
    CSingleton(){}
    CSingleton & operator=(const CSingleton &s);
    static CSingleton* ps;
};
 
CSingleton* CSingleton::ps = NULL;

有2个要点:

1.private的构造函数和=操作符,用于防止类外的实例化和被复制;

2.static的类指针和get方法。

在大多数单线程情况下,以上代码大都会运行得很好,除非遇到中断:

1.当程序运行到tag1 处触发了中断;
2.中断处理程序恰调用的也是getInstance函数。

可想而知,这和多线程的情况类似,假设线程A 运行到tag1处,还没来得及new,此时ps仍然是NULL,线程B(或中断处理程序) 同时也运行到此通过if判断,那么将会实例化2个CSingleton对象,显然是不对的。

为了解决上述问题,自然而然,最容易想到也最常用的方法是加锁,因此getInstance改成这样:

?
1
2
3
4
5
6
7
8
9
static CSingleton* getInstance()
{
    lock();//伪代码
    if (NULL == ps)
    {
        ps = new CSingleton;
    }
    return ps;
}

加了锁以后貌似解决了上述问题,但也同样带来了新的问题:如果程序到处是诸如:

?
1
2
3
CSingleton::instance()->aaaa();
CSingleton::instance()->bbbb();
CSingleton::instance()->cccc();

这样的调用,除了第一次的lock()有用外,后面的都是在做无用功,lock()的代价说大不大,但在某些情况下还是会提高程序延迟,这对追求完美的程序猿来说是完全无法接受的。

于是乎,咱想出了一个办法:

?
1
2
3
4
5
6
7
8
9
10
11
12
static CSingleton* getInstance()
{
    if (NULL == ps)//这里加了次判断,只有第一次才会为true而调用lock()
    {
        lock();//伪代码
        if (NULL == ps)
        {
            ps = new CSingleton;
        }
    }
    return ps;
}

很久以后我才知道,这个方法有个很高大上的名字,叫做双重检查锁定模式,简称DCLP(Double Checked Locking Pattern)。

DCLP很好地解决了多次调用不必要的lock()。

然而,你们以为这样就完了?too young。。

DCLP在多线程下仍然存在2个根本问题:

1.程序的指令执行顺序不确定;
2.编译器优化问题。

先说2,在某些编译器下,以上的两个if判断只会执行一个,甚至一个都不执行,原因是编译器认为至少有一个if判断是多余的,它自动帮助我们优化了代码。

再说1,ps = new CSingleton; 这条语句会被拆分为这样的三个步骤执行:

1.为要new的对象开辟一块内存;
2.构造该对象,填入这块内存;
3.将ps指针指向这块内存。

以上三个步骤,2和3的顺序是不确定的,可能先2后3,也可能先3后2。。。

实际执行时可能是这样的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static CSingleton* getInstance()
{
    if (NULL == ps)
    {
        lock();//伪代码
        if (NULL == ps)
        {    //伪代码
            ps = xx;//step 3
            new sizeof(CSingleton);//step 1
            new CSingleton;//step 2
        }
    }
    return ps;
}

如果编译器按上述顺序执行代码,考虑如下状况:

线程A 执行到step 1还未执行后面的step 2,此时ps非空,但其指向的内存里面的内容还未被构造出来,于此同时线程B 进入这个函数,判断ps非空直接返回ps,但是调用者此时访问的ps内存实际内容CSingleton还没被构造呢,这是一块地址正确大小正确但内部数据不明的东西,当然会出错(调用者一般这么调用:CSingleton::getInstance()->aa();  CSingleton::getInstance()->bb();  CSingleton::getInstance()->cc();........此时的aa,bb,cc是啥玩意儿?)。

这也是为什么加上volatile关键字仍然不可以解决同步问题,volatile只解决了编译器优化问题,却无法控制机器指令执行顺序。

很遗憾的是,C/C++本身在设计时是不考虑多线程问题的,也就是说,要处理多线程问题还要程序猿自己想办法填坑。。

说了这么多,我们要讨论的问题仍然没有解决,庆幸的是,C++ 11提供了内存栅栏技术来解决这个问题,这里不赘述,有兴趣的读者可以自己搜索资料看看,不过是一些api调罢了。

那么,C++ 11 以前的代码如何解决这个问题呢?很不幸,并没有很好的解决方案,一种可行的方案是,程序中不要到处这么调用这个单例对象:

?
1
2
3
CSingleton::getInstance()->aa();
CSingleton::getInstance()->bb();
CSingleton::getInstance()->cc();

而是在程序开始就初始化缓存这个单例对象:

?
1
2
3
4
CSingleton* const g_ps = CSingleton::getInstance();//程序一开始就缓存这个单例对象
g_ps->aa();
g_ps->bb();
g_ps->cc();

但是如此带来的问题是程序一开始就实例化了这个单例对象,对象在整个程序的声明周期存在,这貌似叫饿汉式,而之前那种叫懒汉式,孰轻孰重,只有根据实际情况取舍了。

以上就是小编为大家带来的从C++单例模式到线程安全详解全部内容了,希望大家多多支持服务器之家~

延伸 · 阅读

精彩推荐
  • C/C++c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。...

    jihite5172022-02-22
  • C/C++C语言实现双人五子棋游戏

    C语言实现双人五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    两片空白7312021-11-12
  • C/C++深入C++拷贝构造函数的总结详解

    深入C++拷贝构造函数的总结详解

    本篇文章是对C++中拷贝构造函数进行了总结与介绍。需要的朋友参考下...

    C++教程网5182020-11-30
  • C/C++使用C++制作简单的web服务器(续)

    使用C++制作简单的web服务器(续)

    本文承接上文《使用C++制作简单的web服务器》,把web服务器做的功能稍微强大些,主要增加的功能是从文件中读取网页并返回给客户端,而不是把网页代码...

    C++教程网5492021-02-22
  • C/C++OpenCV实现拼接图像的简单方法

    OpenCV实现拼接图像的简单方法

    这篇文章主要为大家详细介绍了OpenCV实现拼接图像的简单方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    iteye_183805102021-07-29
  • C/C++关于C语言中E-R图的详解

    关于C语言中E-R图的详解

    今天小编就为大家分享一篇关于关于C语言中E-R图的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看...

    Struggler095962021-07-12
  • C/C++c/c++实现获取域名的IP地址

    c/c++实现获取域名的IP地址

    本文给大家汇总介绍了使用c/c++实现获取域名的IP地址的几种方法以及这些方法的核心函数gethostbyname的详细用法,非常的实用,有需要的小伙伴可以参考下...

    C++教程网10262021-03-16
  • C/C++C语言main函数的三种形式实例详解

    C语言main函数的三种形式实例详解

    这篇文章主要介绍了 C语言main函数的三种形式实例详解的相关资料,需要的朋友可以参考下...

    ieearth6912021-05-16