web-dev-qa-db-ja.com

<mutex>を使用せずにC ++ 11でマルチスレッドセーフシングルトンを実装する方法

C++ 11にはマルチスレッドがあるので、mutexを使用せずに遅延初期化シングルトンを実装する正しい方法は何なのか疑問に思いました(パフォーマンス上の理由から)。私はこれを思いつきましたが、tbhはロックフリーコードを書くのがあまり得意ではないので、より良い解決策を探しています。

_// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
# include <atomic>
# include <thread>
# include <string>
# include <iostream>
using namespace std;
class Singleton
{

public:
    Singleton()
    {
    }
static  bool isInitialized()
    {
        return (flag==2);
    }
static  bool initizalize(const string& name_)
    {
        if (flag==2)
            return false;// already initialized
        if (flag==1)
            return false;//somebody else is initializing
        if (flag==0)
        {
            int exp=0;
            int desr=1;
            //bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr)
            bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr);
            if (! willInitialize)
            {
                //some other thread CASed before us
                std::cout<<"somebody else CASed at aprox same time"<< endl;
                return false;
            }
            else 
            {
                initialize_impl(name_);
                assert(flag==1);
                flag=2;
                return true;
            }
        }
    }
static void clear()
{
    name.clear();
    flag=0;
}
private:
static  void initialize_impl(const string& name_)
{
        name=name_;
}
static  atomic<int> flag;
static  string name;
};
atomic<int> Singleton::flag=0;
string Singleton::name;
void myThreadFunction()
{
    Singleton s;
    bool initializedByMe =s.initizalize("1701");
    if (initializedByMe)
        s.clear();

}
int main()
{
    while (true)
    {
        std::thread t1(myThreadFunction);
        std::thread t2(myThreadFunction);
        t1.join();
        t2.join();
    }
    return 0;
}
_

clear()はテスト用であり、実際のシングルトンにはその機能がないことに注意してください。

70
NoSenseEtAl

C++ 11では、手動ロックの必要がなくなります。静的ローカル変数がすでに初期化されている場合、同時実行は待機します。

§6.7 [stmt.dcl] p4

変数の初期化中に制御が同時に宣言に入ると、同時実行は初期化の完了を待機します。

そのため、シンプルには次のようなstatic関数があります。

static Singleton& get() {
  static Singleton instance;
  return instance;
}

これは、C++ 11でも問題なく機能します(もちろん、コンパイラが標準のその部分を適切に実装している限り)。


もちろん、real正解はnotにシングルトンの期間を使用することです。

141
Xeo

私にとって、C++ 11を使用してシングルトンを実装する最良の方法は次のとおりです。

class Singleton {
 public:
  static Singleton& Instance() {
    // Since it's a static variable, if the class has already been created,
    // it won't be created again.
    // And it **is** thread-safe in C++11.
    static Singleton myInstance;

    // Return a reference to our instance.
    return myInstance;
  }

  // delete copy and move constructors and assign operators
  Singleton(Singleton const&) = delete;             // Copy construct
  Singleton(Singleton&&) = delete;                  // Move construct
  Singleton& operator=(Singleton const&) = delete;  // Copy assign
  Singleton& operator=(Singleton &&) = delete;      // Move assign

  // Any other public methods.

 protected:
  Singleton() {
    // Constructor code goes here.
  }

  ~Singleton() {
    // Destructor code goes here.
  }

 // And any other protected methods.
}
37
GutiMac

私見、シングルトンを実装する最良の方法は、「ダブルチェック、シングルロック」パターンを使用することです。これは、C++ 11で移植可能に実装できます。 ダブルチェックロックはC++ 11で修正済み Thisパターンは、作成済みのケースでは高速で、単一のポインター比較のみが必要であり、最初の使用ケースでは安全です。

前の回答で述べたように、C++ 11は静的ローカル変数の構築順序の安全性を保証します C++ 11でローカル静的変数の初期化はスレッドセーフですか? そのため、そのパターンを安全に使用できます。ただし、Visual Studio 2013ではまだサポートされていません: ---(このページの「magic statics」行を参照 。したがって、VS2013を使用している場合は、自分で行う必要があります。

残念ながら、これほど単純なものはありません。静的std :: mutexにはコンストラクターがあり、シングルトンを取得する最初の呼び出しの前に初期化されることが保証されていないため、上記のパターンで参照される サンプルコード はCRT初期化から呼び出すことができません。上記の呼び出しがCRT初期化の副作用である場合。 thatを回避するには、ミューテックスではなく、ミューテックスへのポインタを使用する必要があります。これは、CRTの前にゼロで初期化されることが保証されています初期化が開始されます。次に、std :: atomic :: compare_exchange_strongを使用して、mutexを作成して使用する必要があります。

C++の初期化中に呼び出された場合でも、C++ 11のスレッドセーフなlocal-static-initializationセマンティクスが機能すると想定しています。

そのため、C++ 11のスレッドセーフなlocal-static-initializationセマンティクスを使用できる場合は、それらを使用します。そうでない場合、CRTの初期化中にシングルトンをスレッドセーフにしたい場合でも、いくつかの作業が必要です。

3
Wheezil

コードを意図したとおりに使用していないため、アプローチを読むのは困難です...つまり、シングルトンの一般的なパターンは、instance()を呼び出して単一のインスタンスを取得し、それを使用します(また、あなたは本当にシングルトンが必要です、コンストラクタはパブリックであってはなりません)。

とにかく、私はあなたのアプローチが安全だとは思わない、2つのスレッドがシングルトンを取得しようとすると、フラグを更新する最初のスレッドが唯一の初期化であるが、initialize関数2番目のスレッドで早期に終了し、そのスレッドはシングルトンの使用に進む可能性がありますbefore最初のスレッドが初期化を完了しました。

initializeのセマンティクスが壊れています。 describe/document関数の振る舞いをしようとすると、いくつかの楽しみがあり、単純な操作ではなく実装を説明することになります。文書化は通常、設計/アルゴリズムを再確認する簡単な方法です。最終的にwhatではなくhowを記述したら、設計に戻る必要があります。特に、initializeが完了した後、オブジェクトが実際に初期化されたという保証はありません(戻り値がtrueである場合、場合によってはfalseであるが、常に)。

#pragma once

#include <memory>
#include <mutex>

namespace utils
{

template<typename T>
class Singleton
{
private:
    Singleton<T>(const Singleton<T>&) = delete;
    Singleton<T>& operator = (const Singleton<T>&) = delete;

    Singleton<T>() = default;

    static std::unique_ptr<T> m_instance;
    static std::once_flag m_once;

public:
    virtual ~Singleton<T>() = default;

    static T* getInstance()
    {
        std::call_once(m_once, []() {
            m_instance.reset(new T);
        });
        return m_instance.get();
    }

    template<typename... Args>
    static T* getInstance2nd(Args&& ...args)
    {
        std::call_once(m_once, [&]() {
            m_instance.reset(new T(std::forward<Args>(args)...));
        });
        return m_instance.get();
    }
};

template<typename T> std::unique_ptr<T> Singleton<T>::m_instance;
template<typename T> std::once_flag Singleton<T>::m_once;

}

このバージョンは、c ++ 11標準が100%サポートされることを保証されていない場合、コンカレントフリーに準拠しています。また、「所有」インスタンスをインスタンス化する柔軟な方法も提供します。マジックstatic Wordがc ++ 11以降で十分である場合でも、開発者はインスタンスの作成をさらに制御する必要があるかもしれません。

1
Michal Turlik
template<class T> 
class Resource
{
    Resource<T>(const Resource<T>&) = delete;
    Resource<T>& operator=(const Resource<T>&) = delete;
    static unique_ptr<Resource<T>> m_ins;
    static once_flag m_once;
    Resource<T>() = default;

public : 
    virtual ~Resource<T>() = default;
    static Resource<T>& getInstance() {
        std::call_once(m_once, []() {
            m_ins.reset(new Resource<T>);
        });
        return *m_ins.get();
    }
};
1
Pooja Kuntal