IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> C++单例模式与线程安全 -> 正文阅读

[Java知识库]C++单例模式与线程安全

C++单例模式与线程安全

最简单的单例模式可以是

// single thread safe version
class Singleton {
    public:
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                instance_ = new Singleton(x);
            }
            return instance_;
        }
        void Print() {
            std::cout << this->member_ << std::endl;
        }
    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

然而,在多线程情况下,比如线程A 判断instance_ 为空后,此时线程A暂停执行挂起,而线程B 判断 instance_ 也为空,于是会调用构造函数创建对象,并返回,而线程A继续恢复运行后,同样会调用构造函数创建对象,并修改 instance_ 指针。导致第一个指针被修改,引发线程安全问题。

基于此,可以在判断 instance_ 为空前,先加锁,拿到锁的线程才能进一步判断 instance_ 是否为空,并进一步决定是否创建该对象。代码如下

// thread safe, but inefficiency
class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance_ == NULL) {
                instance_ = new Singleton(x);
            }
            return instance_;
        }
        void Print() {
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

然而,此方法存在效率问题,每次调用 GetInstance()获取该对象指针时,都需要加锁。显然,我们只是希望在首次创建对象的时候才需要加锁,后续的访问不用再加锁(已经创建好了对象),于是很自然的想到在加锁前,再进行一次判断 instance_ 是否为空,代码如下

// double-checked locking
class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    instance_ = new Singleton(x);
                    /*
                    instance_ =       //  point to memory
                        operator new(sizeof(Singleton));  // memory allocate
                    new (instance_) Singleton;  //  construct a object int the allocated memory, may occur exception
                    */
                }
            }
            return instance_b_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

上述方法很好的解决了频繁加锁带来的开销,但是仍然存在一些问题,对于 new 调用,其过程为

  1. 从堆区分配内存
  2. 在分配的内存执行构造函数
  3. 返回其指针

而实际上,经过编译器优化后的代码执行顺序可能并不是这样的,比如

instance_ = new Singleton(x);

可能等价于

instance_ = operator new(sizeof(Singleton));  // memory allocate
new (instance_) Singleton;  // placement new

  1. 从堆区分配内存
  2. 返回堆区的指针
  3. 对指针指向的内存执行构造函数

该情形下,如果线程A在执行到 instance_ = operator new(sizeof(Singleton)); 后,暂停运行挂起,线程B在进入GetInstance() 并判断 instance_ 是否为空时,发现其不为空,于是返回 instance_ 指针,然而该指针是没有经过初始化的,所以线程B对该指针的使用将可能会引起未定义的行为。为了防止发生编译器的优化后的代码执行顺序和我们预期的不一致(上述2,3步调换了顺序),我们希望确保在 new 调用构造函数成功后,再修改 instance_ 指针,代码如下

// in-case exception occur when construct
class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    Singleton* temp = new Singleton(x);
                    instance_ = temp;
                }
            }
            return instance_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

这里我们引入了 temp 指针,用于确保 new 调用成功后,再去修改 instance_ 指针,对new 进行替换后如下

// in-case exception occur when construct
class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    Singleton* temp = operator new(sizeof(Singleton));  // memory allocate
										new (temp) Singleton;  // placement new
                    instance_ = temp;
                }
            }
            return instance_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

然而,对于上述代码,编译器仍可能对进行优化,编译器会发现temp 变量在程序中只是起到一个传递值的作用,可以被优化掉,优化后的代码将直接是 instance_ = new Singleton(x); ,为了防止编译器出现此类优化,我们可以进一步使用 volatile 关键字来防止编译器的优化,代码如下

class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    Singleton* volatile temp = new Singleton(x);
                    instance_ = temp;
                }
            }
            return instance_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

上述代码对temp 变量声明为 volatile,其目的在于告诉编译器,temp相关的代码块不能进行优化,temp相关的指令序列和高级语言看到的应该完全一致。

然而,这里还是可能存在问题,volatile只保证了对temp变量的相关代码的操作顺序,而对temp的成员没有保证,比如下述代码

// in-case exception occur when construct
class Singleton {
    public :
        static Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    Singleton* temp = operator new(sizeof(Singleton));  // memory allocate
										temp->member_ = x;  // construct
                    instance_ = temp;
                }
            }
            return instance_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static Singleton* instance_;  //declare a static member variable    
};

Singleton* Singleton::instance_ = NULL;  //define a static member variable

temp的构造函数在对temp 的成员变量进行赋值时 temp->member_ = x,该操作和 instance_ = temp 可能会因为编译器的优化而重排顺序,如果 instance_ = temp 先于temp->member_ = x 执行

instance_ = temp;
temp->member_ = x

当线程A 在执行完 instance_ = temp; 后暂时挂起,线程B获取到 instance_ 后访问其成员变量 member_时,将会引发未定义的行为(如果是指针,将会发生core),因此我们需要保证 类Singleton 的所有成员变量的相关操作应该也是 volatile 的,于是代码如下

class Singleton {
    public :
        static volatile Singleton* GetInstance(int x = 0) {
            if (instance_ == NULL) {
                std::lock_guard<std::mutex> lock(mtx);
                if (instance_ == NULL) {
                    volatile Singleton* temp = new volatile Singleton(x);
                    instance_ = temp;
                }
            }
            return instance_;
        }
        void Print() { 
            std::cout << this->member_ << std::endl;
        }

    private:
        Singleton(int x = 3) : member_(x) {}
        int member_;
        static volatile Singleton* instance_;  //declare a static member variable    
};

volatile Singleton* Singleton::instance_ = NULL;  //define a static member variable

参考链接:https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:48:23  更:2022-03-06 12:49:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:07:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码