广告
返回顶部
首页 > 资讯 > 精选 >Singleton模式创建、多线程与销毁的方法是什么
  • 772
分享到

Singleton模式创建、多线程与销毁的方法是什么

2023-06-18 00:06:56 772人浏览 独家记忆
摘要

这篇文章主要介绍“Singleton模式创建、多线程与销毁的方法是什么”,在日常操作中,相信很多人在Singleton模式创建、多线程与销毁的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Singl

这篇文章主要介绍“Singleton模式创建、多线程与销毁的方法是什么”,在日常操作中,相信很多人在Singleton模式创建、多线程与销毁的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Singleton模式创建、多线程与销毁的方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

1         Singleton创建

1      GoF Singleton

在GOF著作中对Singleton模式的实现方式如下:

   class Singleton   {   public:   static Singleton *Instance(){                            //1   if( !m_pInstatnce) //2   m_pInstance = new Singleton;//3   return m_pInstance; //4   }   private:   static Singleton *m_pInstatnce;             //5   private:   Singleton();                                                         //6   Singleton(const Singleton&);                             //7   Singleton& operator=(const Singleton&);            //8   ~Singleton();                                                       //9   }   Singleton *Singleton:m_pInstatnce = NULL; //10

在上面的解决方案中,我们只在需要调用时,才产生一个Singleton的对象。这样带来的好处是,如果该对象产生带来的结果很昂贵,但不经常用到时,是一种非常好的策略。但如果该Instance被频繁调用,那么就有人觉得Instance中的判断降低了效率(虽然只是一个判断语句^_^),那么我们就把第5条语句该为

static Singleton m_Instatnce;

如此一来,在Instatnce直接返回&m_Instance,而不用做任何判断,效率也高了。(是不是呢?)

这样修改后,我们将带来灾难性的后果:

首先有可能编译器这关就没法通过,说m_Instance该外部变量无法解决(visural c++6.0)

error LNK2001: unresolved external symbol "private: static class Singleton  Singleton::m_Instance" (?m_Instance@Singleton@@0V1@A)

如果编译器这关通过了就没问题了么?答案是否定的。

***是不管Instance是否用到,该静态变量对象在编译器编译时就产生了,即资源消耗是不可避免的;

第二是无法确保编译器一定先将m_Instance初始化。所以Instance的调用有可能传回一个尚没构造的Singleton对象。这也意味着你无法保证任何外部对象所使用的m_Instance是一个被正确初始化的对象。

2      Meyers Singleton
我们如何解决这个问题呢,实际上很简单。一种非常优雅的做法由Scott Meyers***提出,故也称为Meyers Singleton。它依赖编译器的神奇技巧。即函数内的static对象只在该函数***次执行时才初始化(请注意不是static常量)。

   class Singleton   {   public:   static Singleton *Instance(){                            //1   static Singleton sInstance; //2   return &sInstance; //3   }   private:   Singleton();                                                         //4   Singleton(const Singleton&);                             //5   Singleton& operator=(const Singleton&);            //6   ~Singleton();                                                       //7   }

解二在Instance中定义了一个Static的Singleton对象,来解决Instance中初始化的问题,也很顺利的解决了定义Static成员对象带来的问题。

请注意,解二在VC6中不能编译通过,将有以下的错误:

error C2248: 'Singleton::~Singleton' : cannot access private member declared in class 'Singleton' e:\work\q\a.h(81) : see declaration of 'Singleton::~Singleton'

产生该问题的错误原因是什么呢(请仔细思考^_^)

原因在于在产生static Singleton对象后,编译器会自动产生一个销毁函数__DestroySingleton,然后调用atexit()注册,在程序退出时执行__DestroySingleton。但由于Singleton的析构函数是private,所以会产生访问错误。(应该在以后的编译器中修改了该BUG)

3      Singleton改进

让Instance传回引用(reference)。如果传回指针,调用端有可能讲它delete调。

4      Singleton注意之点

在上面的解法中,请注意对构造函数和析构函数的处理,有何好处(请自己理解,俺懒病又犯了)。

2         多线程

在解一中,如果我们运行在多线程的环境中,该方案是***的么,将会有什么后果呢?

后果就是会造成内存泄漏,并且有可能前后获取的Singleton对象不一样(原因请自己思考,后面有解答)。

为了解决这个问题,将解一的Instance改为如下:

Singleton& Singleton::Instance(){   Lock(m_mutex);            //含义为获取互斥量            //1   If( !m_pInstance ){                                          //2   m_pInstance = new Singleton; //3   }   UnLock(m_mutex);                                            //4   return *m_pInstance;                                     //5   }

此种方法将解决解一运行在多线程环境下内存泄漏的问题,但带来的结果是,当m_mutex被定时,其它试图锁定m_mutex的线程都将必须等等。并且每次执行锁操作其付出的代价极大,亦即是这种方案的解决办法并不吸引人。

那么我们将上面的代码改为如下方式:

Singleton& Singleton::Instance(){   If( !m_pInstance ){                                                      //1   Lock(m_mutex); //含义为获取互斥量 //2   m_pInstance = new Singleton; //3   UnLock(m_mutex); //4   }   return *m_pInstance;                                                 //5   }

这样修改的结果没有问题了么?NO!!!!该方案带来的结果同解一,原因也一样,都将造成内存泄漏。此时“双检测锁定”模式就粉墨登场了。

由Doug Schmidt和Tim Harrison提出了“双检测锁定”(Double-Checked Locking)模式来解决multithread singletons问题。

Singleton& Singleton::Instance(){   If( !m_pInstance ){                                                      //1   Lock(m_mutex); //含义为获取互斥量 //2   If(!m_pInstance) //3   m_pInstance = new Singleton; //4   UnLock(m_mutex); //5   }   return *m_pInstance;                                                 //6   }

请看上面的第三句,这句话是不是具有化腐朽为神奇的力量啊 ^_^

上面的方案就***了么。回答还是NO!!!(各位看官是否已经郁闷了啊,这不是玩我啊?请耐心点,听我细细到来^_^)

如果在RISC机器上编译器有可能将上面的代码优化,在锁定m_mutex前执行第3句。这是完全有可能的,因为***句和第3句一样,根据代码优化原则是可以这样处理的。这样一来,我们引以为自豪的“双检测锁定”居然没有起作用( L)

怎么办?解决呗。怎么解决?简单,我们在m_pInstance前面加一个修饰符就可以了。什么修饰符呢?……

àvolatile(简单吧)

那么我们完整的解法如下:

   class Singleton   {   public:   static Singleton &Instance(){                            //1   if( !m_pInstatnce){ //2   Lock(m_mutex) //3   If( !m_pInstance ) //4   m_pInstance = new Singleton;//5   UnLock(m_mutex); //6   }   return *m_pInstance; //7   }   private:   static volatitle Singleton *m_pInstatnce;            //8   private:   Singleton();                                                         //9   Singleton(const Singleton&);                             //10   Singleton& operator=(const Singleton&);            //11   ~Singleton();                                                       //12   }   Singleton *Singleton:m_pInstatnce = NULL; //13

3         Singleton销毁

在这里,我们就到了Singleton最简单也最复杂的地方了。

为什么说它简单?我们根本可以不理睬创建的对象m_pInstance的销毁啊。因为虽然我们一直没有将Singleton对象删除,但不会造成内存泄漏。为什么这样说呢?因为只有当你分配了累积行数据并丢失了对他的所有reference是,内存泄漏才发生。而对Singleton并不属于上面的情况,没有累积性的东东,而且直到结束我们还有它的引用。在现代操作系统中,当一个进程结束后,将自动将该进程所有内存空间完全释放。(可以参考《effective C++》条款10,里面讲述了内存泄漏)。

但有时泄漏还是存在的,那是什么呢?就是资源泄漏。比如说如果该Singleton对象管理的是网络连接,OS互斥量,进程通信的handles等等。这时我们就必须考虑到Singleton的销毁了。谈到销毁,那可是一个复杂的课题(两天三夜也说不完^_^  开玩笑的啦,大家轻松一下嘛)。

我们需要在恰当的地点,恰当的时机删除Singleton对象,并且还要在恰当的时机创建或者重新创建Singleton对象。

在我们的“解二”中,在程序结束时会自动调用Singleton的析构函数,那么也将自动释放所获取的资源。在大多数情况下,它都能够有效运作。那特殊情况是什么呢?

我们以KDL(keyboard,display,log)模型为例,其中K,D,L均使用Singleton模式。只要keyboard或者display出现异常,我们就必须调用log将其写入日志中,否则log对象不应该创建。对后面一条,我们的Singleton创建时就可以满足。

在前面我们已经说到,在产生一个对象时(非用new产生的对象),由编译器自动调用了atexit(__DestroyObject)函数来实现该对象的析构操作。而C++对象析构是LIFO进行的,即先产生的对象后摧毁。

如果在一般情况下调用了log对象,然后开始销毁对象。按照“后创建的先销毁”原则:log对象将被销毁,然后display对象开始销毁。此时display在销毁发现出现异常,于是调用log对象进行记录。但事实上,log对象已经被销毁,那么调用log对象将产生不可预期的后果,此问题我们称为Dead Reference。所以前面的解决方案不能解决目前我们遇到的问题。

Andrei Alexandrescu提出了解决方案,称为Phoenix Singleton(取自凤凰涅磐典故)

   class Singleton   {   public:   static Singleton &Instance(){                              if( !m_pInstatnce){   Lock(m_mutex)   If( !m_pInstance ){   if(m_destroyed)   OnDeadReference();   else   Create();   }   UnLock(m_mutex);   }   return *m_pInstance;   }   private:   static volatitle Singleton *m_pInstatnce;   static bool m_destroyed;   private:   Singleton();                                                           Singleton(const Singleton&);                               Singleton& operator=(const Singleton&);       ~Singleton(){   m_pInstance = 0;   m_destroyed = true;   }   static void Create(){   static Singleton sInstance;   m_pInstanace = &sInstance;   }   static void OnDeadReference(){   Create();   new (m_pInstance) Singleton;   atexit(KillPhoenixSingleton);   m_destroyed = false;   }   void KillPhoenixSingleton(){   m_pInstance->~Singleton();   }   }   Singleton *Singleton:m_pInstatnce = NULL;   bool m_destroyed =false;

请注意此处OnDeadReference()中所使用的new操作符的用法:是所谓的placement new操作,它并不分配内存,而是在某个地址上构造一个新对象。

这是解决Dead Reference方法之一。如果此时keyboard或者display对象也需要处理Dead Reference问题时,那么上面的OnDeadReference将被频繁调用,效率将会很低。即该问题为:需要提供一种解决方案,用于处理对象的建立过程可以不按照“先创建会销毁”的原则,而应该为其指定一个销毁顺序。

聪明的Andrei Alexandrescu提出了一个“带寿命的Singleton”解决方案。该方案的思想是:利用atexit()的特性;在每次创建一个对象后,将该对象放入到一个链表中(该链表是按照销毁顺序排训的),并同时调用atexit()注册一个销毁函数;该销毁函数从链表中获取最需要销毁的对象进行销毁。

到此,关于“Singleton模式创建、多线程与销毁的方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: Singleton模式创建、多线程与销毁的方法是什么

本文链接: https://www.lsjlt.com/news/294823.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Singleton模式创建、多线程与销毁的方法是什么
    这篇文章主要介绍“Singleton模式创建、多线程与销毁的方法是什么”,在日常操作中,相信很多人在Singleton模式创建、多线程与销毁的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Singl...
    99+
    2023-06-18
  • Druid连接创建及销毁的方法是什么
    这篇文章主要介绍“Druid连接创建及销毁的方法是什么”,在日常操作中,相信很多人在Druid连接创建及销毁的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Druid连接创建及销毁的方法是什么”的疑...
    99+
    2023-07-05
  • c++创建多线程的方法是什么
    在C++中,有多种方法可以创建多线程。以下是其中几种常见的方法: 使用std::thread类:std::thread类是C++标...
    99+
    2023-10-25
    c++
  • java创建多线程的方法是什么
    在Java中,有两种常见的方法来创建多线程:1. 继承Thread类:创建一个继承自Thread类的子类,重写run()方法,并在r...
    99+
    2023-08-16
    java
  • Python线程的创建与常用方法是什么
    这篇文章主要介绍“Python线程的创建与常用方法是什么”,在日常操作中,相信很多人在Python线程的创建与常用方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python线程的创建与常用方法是什么...
    99+
    2023-06-30
  • Linux线程的创建方式是什么
    这篇文章主要介绍“Linux线程的创建方式是什么”,在日常操作中,相信很多人在Linux线程的创建方式是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux线程的创建方式是什么”的疑惑有所帮助!接下来...
    99+
    2023-07-05
  • linux线程的创建方法是什么
    在Linux系统中,可以使用pthread库来创建线程。pthread库是一个POSIX线程库,提供了一系列函数来创建、管理和同步线...
    99+
    2023-08-30
    linux
  • Java中线程的创建方式是什么
    本文小编为大家详细介绍“Java中线程的创建方式是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java中线程的创建方式是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一. 继承Thread可以通过创建...
    99+
    2023-07-05
  • java线程池创建的方法是什么
    在Java中,线程池可以使用以下两种方法来创建: 使用`Executors`类中的静态方法来创建线程池: ExecutorSer...
    99+
    2023-10-25
    java
  • java工厂模式创建对象的方法是什么
    Java工厂模式创建对象的方法是通过工厂类来创建对象。工厂类根据客户端的需求,创建一种或多种不同类型的对象。这种方法将对象的实例化过...
    99+
    2023-10-20
    java
  • Java匿名内部类创建线程的方法是什么
    在Java中,可以使用匿名内部类创建线程的方法是通过继承Thread类或实现Runnable接口。1. 继承Thread类:```T...
    99+
    2023-09-11
    Java
  • Golang并发编程之main goroutine的创建与调度的方法是什么
    今天小编给大家分享一下Golang并发编程之main goroutine的创建与调度的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作