C++并发编程(6):单例模式、once_flag与call_once、call_once实现单例

这篇具有很好参考价值的文章主要介绍了C++并发编程(6):单例模式、once_flag与call_once、call_once实现单例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式

参考博客

【C++】单例模式(饿汉模式、懒汉模式)

C++单例模式总结与剖析

饿汉单例模式 C++实现

C++单例模式(饿汉式)

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结
,一共有23种经典设计模式

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性

设计模式使代码编写真正工程化,设计模式是软件工程的基石脉络,如同大厦的结构一样

单例模式是设计模式中最常用的一种模式,一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

基础要点:

  • 全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
  • 线程安全
  • 禁止赋值和拷贝
  • 用户通过接口获取实例:使用 static 类成员函数

单例的实现主要有饿汉式和懒汉式两种,分别进行介绍

饿汉式

不管你将来用不用,程序启动时就创建一个唯一的实例对象

优点:简单

缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定

示例代码:

//  Hunger_Singleton_pattern
//  Created by lei on 2022/05/13

#include <iostream>
#include <memory>
using namespace std;

class Example
{
public:
    typedef shared_ptr<Example> Ptr;
    static Ptr GetSingleton()
    {
        cout << "Get Singleton" << endl;
        return single;
    }

    void test()
    {
        cout << "Instance location:" << this << endl;
    }

    ~Example() { cout << "Deconstructor called" << endl; };

private:
    static Ptr single;
    Example() { cout << "Constructor called" << endl; };
    Example &operator=(const Example &examp) = delete;
    Example(const Example &examp) = delete;
};
Example::Ptr Example::single = shared_ptr<Example>(new Example);

int main()
{
    Example::Ptr a = Example::GetSingleton();
    Example::Ptr b = Example::GetSingleton();
    a->test();
    b->test();
    cout << "main end" << endl;
    return 0;
}

打印输出:

Constructor called
Get Singleton
Get Singleton
Instance location:0x55d43c9dce70
Instance location:0x55d43c9dce70
main end
Deconstructor called

可以看到拷贝构造函数只调用了一次,并且两个对象内存地址相同,说明该类只能实例化一个对象

饿汉单例模式的静态变量的初始化由C++完成,规避了线程安全问题,所以饿汉单例模式是线程安全的

在大多数情况下使用饿汉单例模式是没有问题的

有缺陷的懒汉模式

懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存

//  Defect_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13

#include <iostream>
#include <thread>
using namespace std;

class Singleton
{
private:
    Singleton()
    {
        cout << "constructor called!" << endl;
    }
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;
    static Singleton *m_instance_ptr;

public:
    ~Singleton()
    {
        cout << "destructor called!" << endl;
    }
    static Singleton *get_instance()
    {
        if (m_instance_ptr == nullptr)
        {
            m_instance_ptr = new Singleton;
        }
        return m_instance_ptr;
    }
    void use() const { cout << "in use" << endl; }
};

Singleton *Singleton::m_instance_ptr = nullptr;     //静态成员变量类内声明类外初始化

int main()
{
    Singleton *instance = Singleton::get_instance();
    Singleton *instance_2 = Singleton::get_instance();

    // thread t1(Singleton::get_instance);
    // thread t2(Singleton::get_instance);
    // thread t3(Singleton::get_instance);
    // thread t4(Singleton::get_instance);

    // t1.join();
    // t2.join();
    // t3.join();
    // t4.join();

    return 0;
}

打印输出:

constructor called!

取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这是个最基础版本的单例实现,存在以下问题

1、当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_instance_ptr
是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr
还是空的,于是也开始实例化单例;这样就会实例化出两个对象

2、类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用,因此会导致内存泄漏

改进的懒汉模式

对应上面两个问题,有以下解决方法:

1、用mutex加锁

2、使用智能指针

//  Improve_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13

#include <iostream>
#include <memory> // shared_ptr
#include <mutex>  // mutex
#include <thread>
using namespace std;

class Singleton
{
public:
    typedef shared_ptr<Singleton> Ptr;
    ~Singleton()
    {
        cout << "destructor called!" << endl;
    }
    Singleton(const Singleton &) = delete;
    Singleton& operator=(const Singleton &) = delete;
    static Ptr get_instance()
    {
        // "double checked lock"
        if (m_instance_ptr == nullptr)
        {
            lock_guard<mutex> lk(m_mutex);
            if (m_instance_ptr == nullptr)
            {
                m_instance_ptr = shared_ptr<Singleton>(new Singleton);
            }
        }
        return m_instance_ptr;
    }

private:
    Singleton()
    {
        cout << "constructor called!" << endl;
    }
    static Ptr m_instance_ptr;
    static mutex m_mutex;
};

// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
mutex Singleton::m_mutex;

int main()
{
    Singleton::Ptr instance = Singleton::get_instance();
    Singleton::Ptr instance2 = Singleton::get_instance();

    // thread t1(Singleton::get_instance);
    // thread t2(Singleton::get_instance);
    // thread t3(Singleton::get_instance);
    // thread t4(Singleton::get_instance);

    // t1.join();
    // t2.join();
    // t3.join();
    // t4.join();

    return 0;
}

打印输出:

constructor called!
destructor called!

只构造了一次实例,并且发生了析构

缺陷是双检锁依然会失效,具体原因可以看下面的文章

https://www.drdobbs.com/cpp/c-and-the-perils-of-double-checked-locki/184405726

推荐的懒汉模式

//  Recommand_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13

#include <iostream>
#include <thread>
using namespace std;

class Singleton
{
public:
    ~Singleton()
    {
        cout << "destructor called!" << endl;
    }
    Singleton(const Singleton &) = delete;
    Singleton &operator=(const Singleton &) = delete;
    static Singleton &get_instance()
    {
        static Singleton instance;
        return instance;
    }

private:
    Singleton()
    {
        cout << "constructor called!" << endl;
    }
};

int main()
{
    Singleton &instance_1 = Singleton::get_instance();
    Singleton &instance_2 = Singleton::get_instance();

    // thread t1(Singleton::get_instance);
    // thread t2(Singleton::get_instance);
    // thread t3(Singleton::get_instance);
    // thread t4(Singleton::get_instance);

    // t1.join();
    // t2.join();
    // t3.join();
    // t4.join();

    return 0;
}

打印输出:

constructor called!
destructor called!

这种方法又叫做 Meyers’ Singleton Meyer’s的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束

这是最推荐的一种单例实现方式:

  • 通过局部静态变量的特性保证了线程安全
  • 不需要使用共享指针,代码简洁
  • 注意在使用的时候需要声明单例的引用 Single& 才能获取对象

once_flag与call_once

参考博客

C++11于once flag,call_once:分析的实现

C++11实现线程安全的单例模式(使用std::call_once)

在多线程编程中,有一个常见的情景是某个任务仅仅须要运行一次

在C++11中提供了非常方便的辅助类once_flag与call_once

once_flag和call_once的声明:

struct once_flag
{
    constexpr once_flag() noexcept;
    once_flag(const once_flag&) = delete;
    once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
  void call_once(once_flag& flag, Callable&& func, Args&&... args);

}  // std

简单示例:

//  once_flag and call_once simple example
//  Created by lei on 2022/05/13

#include <iostream>
using namespace std;

once_flag flag;

void do_once()
{
    call_once(flag, [&]()
              { cout << "Called once" << endl; });
}

int main()
{
    std::thread t1(do_once);
    std::thread t2(do_once);
    std::thread t3(do_once);
    std::thread t4(do_once);

    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

打印输出:

Called once

可以看到4个线程只执行了一次do_once( )函数

call_once实现单例模式

//  Call_once_Singleton_pattern
//  Created by lei on 2022/05/13

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

once_flag cons_flag;

class A
{
public:
    typedef shared_ptr<A> Ptr;

    void m_print() { cout << "m_a++ = " << ++m_a << endl; }

    static Ptr getInstance(int a)
    {
        cout << "Get instance" << endl;
        if (m_instance_ptr == nullptr)
        {
            lock_guard<mutex> m_lock(m_mutex);
            if (m_instance_ptr == nullptr)
            {
                call_once(cons_flag, [&]()
                          { m_instance_ptr.reset(new A(a)); });
            }
        }
        return m_instance_ptr;
    }

    ~A()
    {
        cout << "Deconstructor called" << endl;
    }

private:
    static mutex m_mutex;
    int m_a;
    static Ptr m_instance_ptr;

    A(int a_) : m_a(a_)
    {
        cout << "Constructor called" << endl
             << "m_a = " << m_a << endl;
    }

    A &operator=(const A &A_) = delete;

    A(const A &A_) = delete;
};
A::Ptr A::m_instance_ptr = nullptr;
mutex A::m_mutex;

void test(int aa)
{
    cout << "Go in test..." << endl;
    A::Ptr tp = A::getInstance(aa);
    cout << "tp location:" << tp << endl;
    tp->m_print();
    cout << endl;
}

int main()
{
    thread t1(test, 1);
    thread t2(test, 2);
    thread t3(test, 3);
    thread t4(test, 4);

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    cout << "main end..." << endl;

    return 0;
}

打印输出:

Go in test...
Get instance
Constructor called
m_a = 4
tp location:0x7fd964000f30
m_a++ = 5

Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 6

Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 7

Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 8

main end...
Deconstructor called

看到构造函数只调用了一次,并且类A实例化对象的地址始终相同

上面的两个示例程序中都用到了lambda表达式,call_once通常结合lambda一起使用文章来源地址https://www.toymoban.com/news/detail-571885.html

到了这里,关于C++并发编程(6):单例模式、once_flag与call_once、call_once实现单例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单例模式的八种写法、单例和并发的关系

    为什么需要单例? 节省内存和计算 保证结果正确 方便管理 无状态的工具类:比如日志工具类,不管是在哪里使用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象即可。 全局信息类:比如我

    2024年01月18日
    浏览(40)
  • 【设计模式】详解单例设计模式(包含并发、JVM)

    在软件开发中,经常需要某些类 只能有唯一的实例 ,比如数据库连接。如何才能保证整个应用中只有一个唯一实例?如果靠人为制定的协定来约束,显然不能很好的保证这一点。如果要从 语法上约束 ,在面向对象里面,什么地方能够约束实例的创建? 显然,只有 构造函数

    2024年02月15日
    浏览(26)
  • 【并发专题】单例模式的线程安全(进阶理解篇)

    最近学习了JVM之后,总感觉知识掌握不够深,所以想通过分析经典的【懒汉式单例】来加深一下理解。(主要是【静态内部类】实现单例的方式)。 如果小白想理解单例的话,也能看我这篇文章。我也通过了【前置知识】跟【普通懒汉式】、【双检锁懒汉】、【静态内部类】

    2024年02月14日
    浏览(32)
  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

    设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。 设计模式有很多,本文主要介绍单例模式. 单例模式是一种创建型设

    2024年02月11日
    浏览(44)
  • 《游戏编程模式》学习笔记(六)单例模式 Singleton Pattern

    保证一个类只有一个实例,并且提供了访问该实例的全局访问点。 定义这种东西一般都是不说人话的,要想要理解这句话的意思,我们得把它揉开了才能搞明白。 我们先看前半句 “保证一个类只有一个实例”,单例一般使用类来实现,也就是说,这个单例类,其有且只能有

    2024年02月12日
    浏览(31)
  • 【C++】设计模式-单例模式

    目录 一、单例模式 单例模式的三个要点 针对上述三要点的解决方案 常用的两类单例模式  二、懒汉模式实现 1.基本实现 2.锁+静态成员析构单例 3.双层检查锁定优化 4.双层检查锁定+智能指针 三、饿汉模式实现 1.基础实现 2.嵌套内部类解决内存泄漏 3.智能指针解决内存泄漏

    2024年02月16日
    浏览(29)
  • C++设计模式代码--单例模式

    参考:5. 单例模式(Singleton) (yuque.com) 1、什么是单例模式 保证一个类只有一个实例,并提供一个访问该实例的全局节点; 2、什么情况下需要单例模式 某个类的对象在软件运行之初就创建,并且在软件的很多地方都需要读写这个类的信息;使用单例模式的话,类对象就只要

    2024年02月03日
    浏览(30)
  • C++设计模式:单例模式(十)

    1、单例设计模式 单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个 并且该类只对外暴露一个public方法用来获得这个对象。 单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题 饿汉式:类加载的准备阶段

    2024年04月14日
    浏览(34)
  • 【设计模式】C++单例模式详解

    ⼀个类仅有⼀个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。 那么,我们必须保证: 该类不能被复制;也不能被公开的创造。 对于 C++ 来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。 单例模式又分为 懒汉模式 和 饿汉模式 ,它们

    2024年02月05日
    浏览(37)
  • C++的单例模式

    忘记之前有没有写过单例模式了。 再记录一下: 我使用的代码: 双锁单例: 单例模式的不同实现方式各有优缺点 双检锁(Double Checked Locking): 优点: 线程安全。 在实例已经被创建之后,直接返回实例,避免了每次获取实例时都需要获取锁的开销。 缺点: 代码相对复杂

    2024年02月10日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包