设计模式——C++11实现单例模式(饿汉模式、懒汉模式),与单例的进程

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

本文将介绍单例模式,使用C++11实现多个版本的单例模式,分析各自的优缺点。最后提及如何实现一个单例的进程。

什么是单例模式

单例模式属于创建型模式,提供了一种创建对象的方式。
单例模式确保一个类只有一个实例。通过一个类统一地访问这个实例。
思想:将构造函数设置为私有,通过一个接口获取类对象。如果对象则创建,否则直接返回。

最简单的单例模式——线程不安全

class Singleton_1
{
public:
    static Singleton_1* GetInstance()
    {
        if(m_instance == nullptr)
        {
            m_instance = new Singleton_1();
        }
        return m_instance;
    }

private:
    Singleton_1() = default;
    Singleton_1(const Singleton_1&) = delete;
    Singleton_1(Singleton_1&&) = delete;	/* 不需要 */
	Singleton_1& operator=(const Singleton_1&) = delete;
	Singleton_1& operator=(Singleton_1&&) = delete;	/* 不需要 */
private:
    static Singleton_1* m_instance;
};
Singleton_1* Singleton_1::m_instance = nullptr;

首先把构造函数全部私有,无参构造选择默认生成,拷贝构造和赋值运算符重载选择删除。不需要显式把移动构造和移动赋值删除,因为右值会默认匹配到const左值引用。
GetInstance:判断类内静态成员instance是否为空,空则new一个对象,否则直接返回。
这样的程序在单执行流下是没问题的,但是如果是多线程,可能导致:

  • 线程A进入了GetInstance,判断instance为空,则准备new对象。在执行new之前由于系统调度,线程被切走,记录了上下文,此时属于就绪态,在就绪队列中排队。此时instance仍为空。
  • 线程B进入了GetInstance,判断instance也是为空,准备new对象。此时instance为空,但是有两个线程进入了作用域,就出现了问题。
  • 最终会new出来两个对象,一个丢失在内存中,导致内存泄露,一个后续会被真正使用。

由于m_instance在多线程情况下是临界资源,所以可以考虑加锁。

线程安全的单例模式——多次锁

class Singleton_2
{
public:
    static Singleton_2* GetInstance()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        if(m_instance == nullptr)
        {
            m_instance = new Singleton_2();
        }
        return m_instance;
    }

private:
    Singleton_2() = default;
    Singleton_2(const Singleton_2&) = delete;
	Singleton_2& operator=(const Singleton_2&) = delete;

private:
    static Singleton_2* m_instance;
    static std::mutex m_mutex;
};
Singleton_2* Singleton_2::m_instance = nullptr;

在进入GetInstance的时候就加锁,因为要访问instance这个临界资源,才能保证每次只有一个线程在读或写这个资源。同时注意unique_lock构造时传入的mutex也应该是static的。因为static函数中没有this指针,编译器找不到这个mutex,因此需要声明一个静态的mutex。

但是问题在于,每次调用GetInstance的时候,无论对象是否已经实例化,都需要加锁再解锁。这样是有问题的,我们期望在对象没创建之前就要加锁,创建之后已经没必要了,因为每次只去获取对象。
基于上面的缺点,引入了一种解决方式,双重检测。

线程安全的单例模式——单次锁

class Singleton_3
{
public:
    static Singleton_3* GetInstance()
    {
        if(m_instance == nullptr)
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            if(m_instance == nullptr)
                m_instance = new Singleton_3();
        }
        return m_instance;
    }

private:
    Singleton_3() = default;
    Singleton_3(const Singleton_3&) = delete;
	Singleton_3& operator=(const Singleton_3&) = delete;

private:
    static Singleton_3* m_instance;
    static std::mutex m_mutex;
};

对m_instance做两次检查。

  • 第一次:如果已经实例化,直接返回。否则尝试获取锁。
  • 第二次:获取锁后,再次判断是否已经实例化,如果是则返回,否则再实例化对象。
    这样的好处是,就算多个线程判断到了m_instance为空,进入了if,也能保证线程安全地实例化对象。并且实例化之后,后续获取对象都不需要用到锁,相比第二种方式提高了运行的效率。

Meyers单例模式——C++11

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		static Singleton instance;
		return instance;
	}

private:
	Singleton() = default;
	Singleton(const Singleton& other) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

Meyers单例是利用了C++11的特性而实现的单例模式,大道至简。主要依赖于C++11及以后,静态局部变量的初始化是线程安全的,能够保证安全和效率性。

饿汉模式(Eager Initialization)

饿汉模式是指:一个对象在程序正式被执行之前就实例化。
C/C++中,静态变量或者全局变量是存储在静态区。局部静态变量在运行时分配,全局变量、类内静态成员在编译时分配。因此可以定义m_instance为静态成员变量,并且初始化的时候分配内存。

class Singleton_Eager
{
public:
    static Singleton_Eager* GetInstance()
    {
        return m_instance;
    }
private:
    Singleton_Eager() = default;
    Singleton_Eager(const Singleton_Eager& other) = delete;
	Singleton_Eager& operator=(const Singleton_Eager&) = delete;

private:
    static Singleton_Eager* m_instance;
};
Singleton_Eager* Singleton_Eager::m_instance = new Singleton_Eager();

这样的写法使得每次调用GetInstance获取对象都是线程安全的。

懒汉模式(Lazy Initialization)

懒汉模式是指:在第一次要访问对象的时候再实例化。
本篇文章除了上述的饿汉模式,其他都是懒汉模式。

饿汉和懒汉的区别

饿汉模式
优点:运行时速度快,不存在线程安全的问题。
缺点:程序运行前会分配内存,如果对象比较大,或者要求进程启动时间短的场景可能不适用。

懒汉模式
优点:=-=
缺点:需要控制线程互斥,运行时再加载。

如何实现单例的进程?

在某些场景,比如网络服务程序、嵌入式系统的应用如车机仪表和中控,需要控制同一时间只能有一个程序加载到内存中并运行。
Linux提供了文件锁的接口,可以达到这个目的。文章来源地址https://www.toymoban.com/news/detail-705054.html

/* Operations for the `flock' call.  */
#define	LOCK_SH	1	/* Shared lock.  */
#define	LOCK_EX	2 	/* Exclusive lock.  */
#define	LOCK_UN	8	/* Unlock.  */

/* Can be OR'd in to one of the above.  */
#define	LOCK_NB	4	/* Don't block when locking.  */

/* Apply or remove an advisory lock, according to OPERATION,
   on the file FD refers to.  */
extern int flock (int __fd, int __operation) __THROW;

Apply or remove an advisory lock on the open file specified by fd.  The argument operation is one of the following:
应用或者去除一个建议性质的锁

LOCK_SH  Place a shared lock.  More than one process may hold a shared lock for a given file at a given time.

LOCK_EX  Place an exclusive lock.  Only one process may hold an exclusive lock for a given file at a given time.

LOCK_UN  Remove an existing lock held by this process.

       A call to flock() may block if an incompatible lock is held by another process.  To make a nonblocking request, include LOCK_NB (by ORing) with any of the above
       operations.

       A single file may not simultaneously have both shared and exclusive locks.

       Locks created by flock() are associated with an open file description (see open(2)).  This means that duplicate  file  descriptors  (created  by,  for  example,
       fork(2) or dup(2)) refer to the same lock, and this lock may be modified or released using any of these file descriptors.  Furthermore, the lock is released ei‐
       ther by an explicit LOCK_UN operation on any of these duplicate file descriptors, or when all such file descriptors have been closed.

       If a process uses open(2) (or similar) to obtain more than one file descriptor for the same file, these file descriptors are treated independently  by  flock().
       An attempt to lock the file using one of these file descriptors may be denied by a lock that the calling process has already placed via another file descriptor.

       A  process  may hold only one type of lock (shared or exclusive) on a file.  Subsequent flock() calls on an already locked file will convert an existing lock to
       the new lock mode.

       Locks created by flock() are preserved across an execve(2).

       A shared or exclusive lock can be placed on a file regardless of the mode in which the file was opened.

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
#include <unistd.h>
#include <sys/file.h>

int main()
{
    /* 
        进程单例
        Linux为解决多进程读写同一文件时的冲突,引入了flock
        但是这个锁不是物理上的锁,而是建议性质的
        其他进程可以忽略该锁而直接进行读写
    */
   std::string str = "Hello";

   printf("main : %s\n", str);
    umask(0);
    signal(2, handler);
    std::string fileName = ".lock";
    int fd = open(fileName.c_str(), O_CREAT | O_RDONLY, 0666);
    if(fd < 0)
    {
        printf("fd = %d\n", fd);
        exit(-1);
    }
	
	/* 在打开一个文件后,尝试对其加锁,如果加锁失败,则直接退出 */
    int ret = flock(fd, LOCK_EX | LOCK_NB);
    if(ret < 0)
    {
        printf("ret = %d, what = %s\n",fd, strerror(errno));
        exit(-1);
    }
    while(true)
    {

    }
    return 0;
}

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

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

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

相关文章

  • 单例模式类设计|什么是饿汉模式和懒汉模式

    那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。 高质量干货博客汇总 https://blog.csdn.net/yu_cblog/category_12379430.html?spm=1001.2014.3001.5482 一个类只能创建一个对象,

    2024年02月16日
    浏览(43)
  • 24种设计模式之单例模式(饿汉式、懒汉式)

    单例模式( Singleton Pattern )是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛,例如,总统,班主任等。J2EE标准中的ServletContext 、ServletContextConfig 等、Spring框架应用中的。 特点:构造方

    2024年02月07日
    浏览(47)
  • 设计模式第一课-单例模式(懒汉模式和饿汉模式)

    个人理解:单例模式实际就是通过类加载的方式获取到一个对象,并且保证这个对象在使用中只有一个,不允许再次被创建 1、懒汉模式的基础写法 代码解释: (1)、编写LazySingleton类的时候,需要将成员属性设定为static,这样才会是类属性 (2)、重写构造方法,将其设置

    2024年02月05日
    浏览(43)
  • Java中单例(单态、原子)设计模式(饿汉式/懒汉式)

    先看文章目录,大致了解知识点结构,直接点击文章目录可以跳转到文章指定位置。 设计模式就是设计出来的固定问题的解决方法,描述了在软件设计过程中的一些不断重复发生的问题和解决方案。遇到类似问题的时候可以直接使用现成的模式方案。 ①单例模式中一个类只

    2024年02月04日
    浏览(42)
  • Java设计模式之单例模式详解(懒汉式和饿汉式)

    在开发工作中,有些类只需要存在一个实例,这时就可以使用单例模式。Java中的单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供全局访问点。下面来介绍一下两种常见的单例模式:懒汉式和饿汉式。 懒汉式属于一种延迟加载的单例模式,它的特点是在

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

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

    2024年02月11日
    浏览(54)
  • C++中特殊类的设计与单例模式的简易实现

    对于这种特殊类的设计我们一般都是优先考虑私有构造函数。 然后对于一些特殊要求就直接通过静态成员函数的实现来完成。  这里选择禁掉拷贝构造函数和拷贝函数是为了防止将已创建的对象去拷贝构造新的对象。  这里如果没有禁掉operator new和operator delete的话就会导致以

    2024年01月18日
    浏览(47)
  • Java单例模式的五种实现方式 懒汉式 饿汉式 双重校验锁 静态变量 静态内部类 枚举实现单例模式等

    Java单例模式是一种设计模式,用于确保一个类只有一个实例,并提供全局访问点以获取该实例。它通常用于需要共享资源或控制某些共享状态的情况下。 懒汉式:在类加载的时候就创建对象,要再调用方法时才创建对象,减少内存开销。 饿汉式:再类加载的时候就实例化对

    2024年04月27日
    浏览(36)
  • 【单例模式】饿汉模式和懒汉模式的单例模式

    设计模式是一种 在软件设计中经过验证的解决问题的方案或者模版 。它们是从实践中总结出来的,可以帮助解决常见的设计问题,提高代码的重用性、维护性和扩展性。 设计模式可以分为三大类: 创建型模式(Creational Patterns) :创建型模式关注对象的实例化过程,包括如

    2024年02月16日
    浏览(68)
  • 单例模式(饿汉式单例 VS 懒汉式单例)

    所谓的单例模式就是保证某个类在程序中只有一个对象 1.构造方法私有化(保证对象的产生个数)         创建类的对象,要通过构造方法产生对象        构造方法若是public权限,对于类的外部,可以随意创建对象,无法控制对象个数       构造方法私有化,这样类的外

    2024年02月09日
    浏览(41)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包