pthread_once重塑singleton模式

单件模式是非线程安全的:

 

// Single threaded version
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }
 
    // other functions and members...
}


 

这段在使用多线程的情况下无法正常工作。在多个线程同时调用getHelper()时,必须要获取锁,否则,这些线程可能同时去创建对象,或者某个线程会得到一个未完全初始化的对象。
锁可以通过代价很高的同步来获得,就像下面的例子一样。

// Correct but possibly expensive multithreaded version
class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }
 
    // other functions and members...
}



只有getHelper()的第一次调用需要同步创建对象,创建之后getHelper()只是简单的返回成员变量,而这里是无需同步的。 由于同步一个方法会降低100倍或更高的性能[2], 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。许多程序员一下面这种方式进行优化:
检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
获取锁
第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
否则,初始化并返回变量。

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }
 
    // other functions and members...
}



直觉上,这个算法看起来像是该问题的有效解决方案。然而,这一技术还有许多需要避免的细微问题。例如,考虑下面的事件序列:
线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存(缓存一致性)),程序很可能会崩溃。
以上内容出自伟大的维基百科:http://zh.wikipedia.org/zh/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F

使用pthread_once语义可以解决上述问题:The  purpose  of  pthread_once(pthread_once_t *once_control, void (*init_routine) (void))  is  to ensure that a piece of initialization code is executed at most once. The once_control argument points to a static or extern variable statically initialized to PTHREAD_ONCE_INIT.The first time pthread_once is called with a given once_control argument, it calls init_routine with  no  argument  and  changes  the value  of  the once_control variable to record that initialization has been performed. Subsequent calls to pthread_once with the sameonce_control argument do nothing.

线程安全的例子:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<boost/noncopyable.hpp>
using namespace std;
using namespace boost;
template<typename T>
class singleton:noncopyable{
    public:
        static T& instance(){
            pthread_once(&ponce,&singleton::init);//第一次调用才会执行init,此后将改变ponce并将已经执行记录存入ponce
            return *value;
        }
    private:
        singleton();
        ~singleton();
        static void init(){
            value=new T();//这里只能调用T的默认构造函数,若要用户指定T的构造方式,可能需要模板特化...不怎么熟悉...汗
        }
    private:
        static pthread_once_t ponce;
        static T* value;
};
template<typename T>//静态成员类外初始化
pthread_once_t singleton<T>::ponce=PTHREAD_ONCE_INIT;//ponce初始化
template<typename T>
T* singleton<T>::value=NULL;


class test{//测试对象
    public:
        void show(){
            cout<<"show()"<<endl;
        }
};
int main(){
    test& p=singleton<test>::instance();//注意使用方法
    p.show();
    test& q=singleton<test>::instance();
    if(&p==&q)
        cout<<"singleton success"<<endl;
    else
        cout<<"singleton failure"<<endl;
    return 0;
}



程序输出:
show()
singleton success

posted @ 2013-11-01 18:53  pangbangb  阅读(455)  评论(0编辑  收藏  举报