热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

用C++实现单例模式3——如何在不使用锁和C++11的情况下,用C++实现线程安全的Singleton

如题所示,在这里主要讲的是,如何在不使用锁和C11的情况下,用C实现线程安全的Singleton。有四种方式来实现:1.At
如题所示,在这里主要讲的是,如何在不使用锁和C++11的情况下,用C++实现线程安全的Singleton。


有四种方式来实现:


1.Atomic Singleton


2.UNIX平台下的pthread_once


3.static object


4.local static

需要区分三种状态:

*对象已经构造完成


*对象还没有构造完成,但是某一线程正在构造中


*对象还没有构造完成,也没有任何线程正在构造中





1.Atomic Singleton

在C++11之前的版本下,除了通过锁实现线程安全的Singleton外,还可以利用各个编译器内置的atomic operation来实现。(假设类Atomic是封装的编译器提供的atomic operation)。

template
class Singleton
{
public:
static T& getInstance()
{while (true){if (ready_.get()){return *value_;}else{if (initializing_.getAndSet(true)){// another thread is initializing, waiting in circulation}else{value_ = new T();ready_.set(true);return *value_;}}}
}
private:Singleton();~Singleton();static Atomic ready_;static Atomic initializing_;static T* value_;
};template
Atomic Singleton::ready_(false);template
Atomic Singleton::initializing_(false);template
T* Singleton::value_ = NULL;
2.UNIX平台下的pthread_once


    如果是在unix平台的话,除了使用atomic operation外,在不适用C++11的情况下,还可以通过pthread_once来实现Singleton。
    pthread_once的原型为:

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))

APUE中对于pthread_once是这样说的:
如果每个线程都调用pthread_once,系统就能保证初始化例程init_routine只被调用一次,即在系统首次调用pthread_once时。
所以,我们就可以这样来实现Singleton了。

template
class Singleton : noncopyable
{
public:
static T& getInstance()
{threads::pthread_once(&once_control_, init);return *value_;
}
private:static void init(){value_ = new T();}Singleton();~Singleton();static pthread_once_t once_control_;static T* value_;
};template
pthread_once_t Singleton::once_control_ = PTHREAD_ONCE_INIT;template
T* Singleton::value_ = NULL;
       如果需要正确的释放资源的话,可以在init函数里面使用glibc提供的atexit函数来注册相关的资源释放函数,从而达到了只在进程退出时才释放资源的这一目的。


3.static object
不用锁和C++11,那么可以通过atomic operation来实现,但是有人会说atomic不是夸平台的,各个编译器的实现不一样。那么其实通过static object来实现也是可行的。

template
class Singleton
{
public:static T& getInstance(){return *value_;}
private:Singleton();~Singleton();class Helper{public:Helper(){Singleton::value_ = new T();}~Helper(){delete value_;value_ = NULL;}}; //class Helperfriend class Helper;static T* value_;static Helper helper_;
};//class Singletontemplate
T* Singleton::value_ = NULL;template
typename Singleton::Helper Singleton::helper_;

在进入main之前就把Singleton对象构造出来就可以避免在进入main函数后的多线程环境中构造的各种情况了。这种写法有一个前提就是不能在main函数执行之前调用getInstance,因为C++标准只保证静态变量在main函数之前之前被构造完成。
可能有人会说如果helper的初始化先于value_初始化的话,那么helper_初始化的时候就会使用尚没有被初始化的value_,这个时候使用其返回的对象就会出现问题,或者在后面value_“真正”初始化的时候会覆盖掉helper_初始化时赋给value_的值。
实际上这种情况不会发生,value_的初始化一定先于helper_,因为C++标准保证了这一行为:
The storage for objects with static storage duration (basic.stc.static) shall be zero-initialized (dcl.init) before any other initialization takes place. Zero-initialization and initialization with a constant expression are collectively called static initialization; all other initialization is dynamic initialization. Objects of POD types (basic.types) with static storage duration initialized with constant expressions (expr.const) shall be initialized before any dynamic initialization takes place. Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.

     stackoverflow中的一个问题也讨论了相关的行为,When are static C++ class members initialized?    http://stackoverflow.com/questions/1421671/when-are-static-c-class-members-initialized


4.local static
上面一种写法只能在进入main函数后才能调用getInstance,那么有人说,我要在main函数之前调用怎么办?
嗯,办法还是有的。这个时候我们就可以利用local static来实现,C++标准保证函数内的local static变量在函数调用之前被初始化构造完成,利用这一特性就可以达到目的:

template
class Singleton
{
private:Singleton();~Singleton();class Creater{public:Creater(): value_(new T()){}~Creater(){delete value_;value_ = NULL;}T& getValue(){return *value_;}T* value_;};//class Creater
public:static T& getInstance(){static Creater creater;return creater.getValue();}
private:class Dummy{public:Dummy(){Singleton::getInstance();}};//classstatic Dummy dummy_;
};template
typename Singleton::Dummy Singleton::dummy_;

这样就可以了。dummy_的作用是即使在main函数之前没有调用getInstance,它依然会作为最后一道屏障保证在进入main函数之前构造完成Singleton对象。这样就避免了在进入main函数后的多线程环境中初始化的各种问题了。
     但是此种方法只能在main函数执行之前的环境是单线程的环境下才能正确工作。
     实际上,上文所讲述了各种写法中,有一些不能在main函数之前调用。有一些可以在main函数之前调用,但是必须在进入main之前的环境是单线程的情况下才能正常工作。具体哪种写法是属于这两种情况就不一一分析了。总之,个人建议最好不要在进入main函数之前获取Singleton对象。因为上文中的各种方法都用到了staitc member,而C++标准只保证static member在进入main函数之前初始化,但是不同编译单元之间的static member的初始化顺序却是未定义的, 所以如果在main之前就调用getInstance的话,就有可能出现实现Singleton的static member还没有初始化就被使用的情况。
     如果万一要在main之前获取Singleton对象,并且进入main之前的环境是多线程环境,这种情形下,还能保证正常工作的写法只有C++ 11下的Meyers Singleton,或者如g++ 4.0及其后续版本这样的编译器提前支持内存模型情况下的C++ 03也是可以的。



boost下singleton模式实现:

template
struct Singleton
{
struct object_creator
{object_creator(){Singleton::instance();}inline void do_nothing()const {}
};
static object_creator create_object;
public:typedef T object_type;static object_type& instance(){static object_type obj;create_object.do_nothing();return obj;}
};
template
typename Singleton::object_creator Singleton::create_object;
// int main()
// {
// int sint = Singleton::instance();
// return 0;
// }
代码分析,可以参考fullsail博客:BOOST的Singleton模版详解  http://www.cnblogs.com/fullsail/archive/2013/01/03/2842618.html










推荐阅读
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
author-avatar
从前泪流光e_446
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有