pthread_once singletonモードの再構築

4491 ワード

シングル・モードはスレッド以外で安全です.
 
// 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()は単純にメンバー変数を返すだけで、ここでは同期する必要はありません.1つの方法を同期すると100倍以上のパフォーマンスが低下する[2]ため、ロックの取得と解放を呼び出すたびにオーバーヘッドは回避できるようだ.初期化が完了すると、ロックの取得と解放は不要になる.多くのプログラマは、変数が初期化されているかどうかを確認し(ロックを取得しない)、初期化されている場合はすぐにこの変数に戻ります.取得ロック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