热门标签 | 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










推荐阅读
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社区 版权所有