C++プログラミングのシングルトンパターン

コンテンツ

1.スレッドセーフ

2.スレッドの安全性の例

2.1例1:

2.2例2:

3.空腹モードと怠惰モードの違い


いわゆるシングルトンパターンは、クラスがメモリ内に1つだけのオブジェクトを持っていることを意味します。

1.スレッドセーフ

いわゆるスレッドセーフとは、プログラム全体で複数のスレッドが作成され、このコードを実行する各マルチスレッドプログラムの結果が単一のスレッドと同じである場合、これらの複数のスレッドが同じコードを実行する可能性があることを指します。これを実行しているスレッドプログラムコードの結果は同じであり、変数値は期待どおりであるため、マルチスレッドプログラムはスレッドセーフです。

例:

main関数は、2つのスレッドを作成し、2つのスレッドにObjectクラスのstaticメソッドを呼び出させ、staticメソッドによって返されたオブジェクトアドレスを出力します。

#include <iostream>
#include <thread>
using namespace std;
//1、线程安全的情况
class Object
{
private:
	int value;
	static Object instance;//静态变量需要类外初始化
	static int num;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		return instance;
	}
};
Object Object::instance(10);
void funa()
{
	Object& obja = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

プログラムの実行結果:

上記のコードが実行された後、2つの印刷されたオブジェクトアドレスは同じになります。

説明:

静的オブジェクトインスタンスは、メイン関数に入る前にデータ領域に構築されており、その値= 10が設定されています。その後、メイン関数に入った後、thraスレッドかthrbスレッドかに関係なく、この静的オブジェクトにのみアクセスできます。オブジェクトインスタンスであり、変更は行われないため、スレッドセーフです。

2.スレッドの安全性の例

次の例でスレッドの不安定さの2つのケースを説明し、空腹モードと怠惰モードとは何かを説明します。

2.1例1:

空腹の人モードを説明する前に、まず、プログラムの実行プロセス中に静的変数がどのように初期化されるかを理解しましょう。これは、後続の例を理解するのに便利です。

プログラムがリテラル定数と低レベルの変数を使用して静的変数を初期化することは異なります。

例:

void funa(int x)
{
    static int a=10;
}
int main()
{
     funa(10);
}

静的定数:main関数に入る前にstatic int a = 10に遭遇すると、変数aがデータ領域に配置され、特定のサイズのスペースが開かれ、初期値10が割り当てられます。 (これはスレッドセーフです)。

例:

void funb(int x)
{
    static int b=x;
}
int main()
{
    funb(10);
}

静的変数:main関数に入る前にstatic int b = xに遭遇します。まず、データ領域のこの変数bにメモリを割り当てます。割り当てる量がわからないため、最初に割り当てないでください。現時点では、静的変数の中にフラグfigがあります=0は割り当てがないことを意味します。main関数の後でfunb(10)を呼び出し、データ領域に移動して割り当てます。このとき、figフラグが0から1に変わり、初期値が割り当てられたことを示します。したがって、変数がある場合、データ領域は最初にフラグfigを変更し、次に値を変更します(プログラムに複数のスレッドがあり、データ内の変数のフラグビットfigを変更するために競合するため、これはスレッドセーフではありません)範囲)。

プログラムが静的変数を処理する方法を理解した後、次の例を見てください。

main関数は、2つのスレッドを作成します。これらのスレッドは、それぞれObjectクラスの静的メソッドを呼び出し、パラメーターをxに渡し、静的オブジェクトを作成してオブジェクトを返します。

//饿汉模式
class Object
{
public:
	int value;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance(int x)//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		//直接在静态函数中构建这个静态对象并返回其本身
		static Object instance(x);
		return instance;
	}
};
void funa()
{
	Object& obja = Object::GetInstance(10);//调用静态方法返回静态对象本身
	cout << obja.value << endl;
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance(20);//调用静态方法返回静态对象本身
	cout << objb.value << endl;
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

プログラムの実行結果:

 ファーストラン:

2回目の実行:

上記の例はスレッドセーフではないことがわかります。当初は、thraスレッドがパラメーター10をxに渡し、value = 10の静的オブジェクトを作成してから、静的オブジェクトを返すことを期待していました。thrbスレッドは、パラメーター20をxに渡し、value = 20の静的オブジェクトを作成して、静的オブジェクトを返します。しかし、プログラムを実行した結果、オブジェクトは1つしかなく、valueの値は未定義であり、期待した結果ではありません。

理由:

static Object instance(x);この文は、メイン関数に入る前にデータ領域にアドレススペースを割り当て、メイン関数に入った後にスレッドを開始し、2つのスレッドがObjectクラスの静的メソッドを同時に実行します(静的メソッドの共有は1つだけです)、リソースの競合の問題があり、0から1までのfigをマークする権利をめぐって競合します。このスレッドは機会をつかみ、独自の値を値に割り当て、次にそうでないスレッドを割り当てます。再度アクセスするとfigが見つかります。フラグは1に設定されており、割り当てられていることを示しているため、静的メソッドを終了することしかできません。

最後に、Hungry Manモードとは何かについて話しましょう。静的オブジェクトを作成する上記の例から、Hungry Manモードは、設計したクラスがロードを開始する限り、シングルトンを初期化して確実にする必要があることを意味することがわかります。複数のスレッドがこの一意のリソースにアクセスすること。、シングルトンはすでに存在します(上記の例を見てください:Objectクラスのロードが開始されると、複数のスレッドがこれを呼び出すように、静的Object instance(x)オブジェクトをすばやく初期化する必要があります、静的オブジェクトがすでに存在し、スレッドが他の変更を必要としない場合の一意の静的メソッド)。

2.2例2:

例:

main関数は2つのスレッドを開始し、2つのスレッドはそれぞれObjectクラスのstaticメソッドを呼び出します。静的ポインターpobjが空の場合、オブジェクトオブジェクトはヒープ領域から構築され、静的な反対側のポインターpobjが返されます。ヒープ領域に構築されたObjectオブジェクトに、2つのスレッドがそれぞれポインタが指すオブジェクトのアドレスを出力します。

class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

プログラムの実行結果:

ファーストラン:

2回目の実行:

プログラムの実行結果から、2つのスレッドによって返されるオブジェクトアドレスは同じではないことがわかりますが、スレッドセーフの下で複数のスレッドが同じプログラムを実行する場合、返されるオブジェクトは1つだけであり、アドレスは一意であると予想されます。 。これが現在発生している理由は次のとおりです。

両方のスレッドが静的メソッドGetInstance()に入り、両方ともpobjポインターが空であることを検出したため、両方ともIfステートメントを入力し、スリープ時間を設定してから、1つ実行しましたpobj = new Object(10);オブジェクトを作成しましたpobjの場合、オブジェクトのアドレスを出力するために戻り、次に別のスレッドがpobj = new Object(10);;を実行し、別のオブジェクトを作成してpobjを再割り当てし、オブジェクトのアドレスを出力します。

したがって、上記の例からわかるように、いわゆるレイジーモードとは、設計したクラスのロードが開始されたときに、シングルトンをすばやく初期化する必要はなく、初めて使用されるまで待つことを意味します(つまり、怠惰だと言われています)。レイジーモードでは、スレッドセーフを確保し、オブジェクトを1つだけ作成する場合は、オブジェクトを作成するコードの部分をロックできます。

変更されたコードは次のとおりです。
 

#include <mutex>
std::mutex mtx;
class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		std::lock_guard<std::mutex>lock(mtx);
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

3.空腹モードと怠惰モードの違い

(1)空腹の男モード:

オブジェクトはクラスがロードされたときに作成されており、プログラムは呼び出されたときに以前に作成されたインスタンスオブジェクトを直接取得します。

このように、構築されたオブジェクトは静的であるため、常にデータ領域のスペースを占有しますが、呼び出し時にオブジェクトを構築する時間を節約できます。

(2)レイジーモード:

オブジェクトは、静的メソッドにアクセスするときにのみ作成されます。

このメソッドは、使用する必要がある場合にのみ構築されます。これにより、ある程度のスペースが節約され、オブジェクトが常にメモリ内のスペースを浪費するのを防ぎますが、使用すると、一定の時間がかかり、プログラムが遅くなります。 。

このモードは安全ではなく、制御のためにロックが必要です。

おすすめ

転載: blog.csdn.net/m0_54355780/article/details/123188535