Singletonパターンはシンプルに見えますが、考慮すべき多くの問題があります。
ソフトウェアシステムでは、多くの場合、このような特別なクラスを持って、彼らは唯一つのインスタンスは、その論理的正しさ、そして優れた効率性を確保するために、システム内に存在していることを確認する必要があります。
したがって我々は、従来の構造のフィルタバイパス(新しいユーザーオブジェクトを許可していない)に、クラスのインスタンスを1つだけ確実にする機構が提供される方法を検討しなければなりません。
シナリオ:
- Windowsのタスクマネージャ(タスクマネージャ)は、シングルモードの典型的な例では、両方のタスクマネージャを開くことができません。Windowsのごみ箱は、同じ理由です。
- アプリケーションログアプリケーションは、一般的な実施形態のシングルモード動作するファイルの唯一のインスタンスで達成することができます。
- 設定ファイルを読み込み、読み込み設定項目公開され、どこにでも読むための場所を使用することができ、あらゆる場所に再びそれを読んでする必要が設定できません。
データベース接続プール、マルチスレッドスレッドプール。
実現
Singletonパターンを達成することは、いくつかの一般的な実装で見てみましょう、をたくさん持っています。いくつかの実装はいくつかのシーンに適したかもしれないが、それを使用することはできないという意味ではありません。
] [スレッド危険を達成
class Singleton{
public:
static Singleton* getInstance(){
// 先检查对象是否存在
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton(const Singleton& other);
static Singleton* m_instance; //静态成员变量
};
Singleton* Singleton::m_instance=nullptr; //静态成员需要先初始化
これは、最も古典的なシングルトンの実装、コンストラクタとコピーコンストラクタを非公開に設定されているですが、また、オブジェクトが最初の呼び出しのgetInstance(の時に生成される遅延初期化の方法を使用しています)で呼ばれていませんオブジェクトを生成しない、それはメモリを占有しません。しかし、マルチスレッドの場合には、この方法は安全ではありません。
分析:通常の状況下では、のgetInstance(にスレッドA・コールの場合)、m_instanceが初期化され、スレッドB、その後のgetInstance()を呼び出すには、新しいを実行しないであろう、そして構築されたオブジェクトが直接前に返されます。しかしながら、このような場合は、スレッドAが実行m_instance =新しいシングルトン()はまだ完了存在しない場合、この時間m_instanceは依然としてnullptr、スレッドBはまた、ある2つのオブジェクト、Aおよびスレッドを有することになるm_instance =新しいシングルトン()は、実施されていますBが同じオブジェクトを使用している可能性があり、それはまた、メモリリークが発生している間、手続きエラーにつながる可能性が二つのオブジェクトであってもよいです。
[価格が高すぎるロック、2つのスレッドセーフを実現]
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
Lock lock; //伪代码 加锁
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
分析:上記の状況の文言二つの新しい実行スレッドを発生せず、スレッドA m_instance =新しいシングルトン()、スレッドBを実行するときに、あなたがたgetInstance()を呼び出した場合、ロックされたオフィスでブロックされますロックの解除後に待機しているスレッドの実行。だから、スレッドセーフです。
getInstanceへの各呼び出しは()ロックを解除するためにロックされ、そしてこの手順は、最初に新しいシングルトン(なのでしかし、そのようなアプローチの性能)が、高くない限りm_instanceが作成されるよう、必要があるが、どんなに多くのスレッド同時にアクセスでは、使用している場合(m_instance == nullptr)十分な(読むだけの操作、ロックなし)、無スレッド安全性の問題を判断しますが、パフォーマンスの問題の後にロックがある追加されます。
[3つのダブルチェックロックを実現]
上記のアプローチは、あなたがスレッドにアクセスしたいときだけ-ん、最初のロック再び、これはロックの不必要な消費につながることです、そして、それはそれは、(m_instance == nullptr)判断した場合は、次のことが可能ですもしそうなら、あなたはロックああを説明する必要はありません!これは、ダブルチェックロックである、いわゆるダブルチェックロッキングの(DCL)、DCLのアイデアです。
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
//先判断是不是初始化了,如果初始化过,就再也不会使用锁了
if(m_instance==nullptr){
Lock lock; //伪代码
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
これは素晴らしいですね!同じで実現し、必要な最初の時間をロックし、そしてそれの後にのみ使用されます。
時間の長い期間のために、抜け穴を発見されたとき、多くの人が、2000年に、混乱するだけでなく、各言語では、発見されました。その理由は、順序(質問コンパイラ)メモリの読み取りと書き込みが不足しています。
分析:m_instance =新しいシングルトン()文は、3つのステップに分けることができるが行われます。
- シングルトンメモリが必要なオブジェクトのタイプを割り当てられます。
- メモリ割り当てのオブジェクトタイプを構築するシングルトン。
- ポインタm_instanceに割り当てられたメモリアドレス。
これら三つのステップが順次実行されるが、実際にのみ、最初必ずしもステップ2、3、実行されるステップ1で決定することができると思うかもしれません。ここでの問題は発生します。コールm_instance =新しいシングルトン()で実行されるスレッドAは、3,2の順である場合、ステップ3を実行することだけシングルトンタイプがメモリに割り当てられ、場合(ケースm_instanceをnullptrない)スイッチスレッドBが直接m_instanceは、オブジェクトを取得し、このオブジェクトが実際に構築されていません返す実行されるように、スレッドBに、原因m_instanceのnullptrではありません!!深刻なバグが起こりました。
コンパイラは、順序が順序の調整の結果には影響しませんが、実際には複数のスレッドで問題になることがあり人のためのコードのルックスをコードを調整し、最適化する必要があります。
4【実現のクロスプラットフォームのC ++ 11版]を達成します
JavaとC#は、この問題を発見した後、変数を宣言する際、コンパイラは見た後、あなたがいないリオーダー(メモリを割り当てることを確認することができ、この場所を知って、揮発性の修正を追加するために、キーワード揮発性、m_instanceを追加コンストラクタの実装、およびその後の割り当て後に完了しています)。
標準が、修正されていなかったので、VC ++バージョン2005のC ++用としても、このキーワードに参加しましたが、それは(唯一のマイクロソフトのプラットフォームをサポートしています)、クロスプラットフォームません。
そして、C ++バージョン11に、最終的には私たちは、私たちは、クロスプラットフォーム・ソリューションを達成するのを助けるために、このような仕組みを持っています。
//C++ 11版本之后的跨平台实现
// atomic c++11中提供的原子操作
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
/*
* std::atomic_thread_fence(std::memory_order_acquire);
* std::atomic_thread_fence(std::memory_order_release);
* 这两句话可以保证他们之间的语句不会发生乱序执行。
*/
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
pthread_once 5 [機能]を実現
Linuxでは、とpthread_once()関数は、関数が一度だけ実行されることを保証します。
声明:
int pthread_once(pthread_once_t once_control, void (init_routine) (void));
機能:この関数はのPTHREAD_ONCE_INIT once_controlの初期値使用しています
。このプロセスの実行順序で一度だけ実行されinit_routine()関数を確実にするための変数を。
例としては、次のとおりです:
class Singleton{
public:
static Singleton* getInstance(){
// init函数只会执行一次
pthread_once(&ponce_, &Singleton::init);
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton(const Singleton& other);
//要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)
static void init() {
m_instance = new Singleton();
}
static pthread_once_t ponce_;
static Singleton* m_instance; //静态成员变量
};
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::m_instance=nullptr; //静态成员需要先初始化
6 - [C ++最も単純なクロスプラットフォームソリューションの11版
4つのプログラムを少しトラブルを達成するため、プログラムは5クロスプラットフォームを実現することはできません。実際には、C ++の11はすでに彼はまた、パラメータのonce_flagを必要とし、同じ、マルチスレッド環境で一度だけ呼び出される関数を確保するためのstd ::なcall_onceの方法を提供します。使用方法と似たとpthread_once、およびクロスプラットフォームのサポート。
実際には、最も簡単な解決策の一つがあります!
だけでなく、ローカルの静的変数は一度だけ初期化されますが、また、スレッドセーフ。
class Singleton{
public:
// 注意返回的是引用。
static Singleton& getInstance(){
static Singleton m_instance; //局部静态变量
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton(const Singleton& other);
};
この実施例は、シングル・マイヤーズシングルトンと呼ばれています。この方法は非常に簡単で、かつ非常に完璧な、しかしノートです。
- GCC 4.0コンパイラた後、このようなアプローチをサポートしています。
- C ++ 11下と(例えば、C ++ 14のような)それ以降のバージョンが正しくマルチスレッド。
- C ++は、とても11の前に書かれていません。