Linuxマルチスレッドサーバプログラミング基礎C++11同時プログラミング学習
6572 ワード
前言
C++では従来バージョンではスレッドはサポートされていません.pthreadなどの使用スレッドが必要です.C++自身のスレッドを使用できれば,プログラムを統一して簡潔にすることができる.
ヘッダファイルヘッダファイルにはstd::threadクラスとstd::this_threadクラス.スレッドを管理する関数です.スレッドを実装する主なファイルです. ヘッダファイルにはstd::atomicおよびstd::atomic_が含まれています.flagクラスは、原子操作を実現する主なファイルです. は、反発に関連するクラスと関数を含む. にはfutureクラスと関連する関数が含まれています. は、条件付き変数のクラスを含む.
以上がc++11のスレッド部分です.pthreadとc++threadについては多くの議論があるが、プラットフォームをまたぐc++threadにとっては、より標準的なようだ.
この本はc++11のスレッドを学ぶことをお勧めします.https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details
hello worldスレッド
threadクラスの単純な認識
joinを除いて終了を待つには、detachを使用してスレッドの終了を待たないことができます.
この例では、スレッドの終了を待たない(detach()②)と決定しているので、oops()関数の実行が完了した場合③は、新しいスレッドの関数がまだ実行されている可能性があります.スレッドがまだ実行されている場合はdo_が呼び出されますsomething(i)関数①は、破棄された変数にアクセスします.単一スレッドプログラムのように、関数が完了した後もローカル変数のポインタまたは参照を保持し続けることができます.もちろん、これは決して良いアイデアではありません.このような状況が発生すると、エラーは明らかではなく、マルチスレッドがエラーしやすくなります.
スレッドの終了を待つにはどうすればいいですか?
スレッドを待つ必要がある場合は、関連するstd::threadインスタンスはjoin()を使用する必要があります.インベントリ2.1でmy_thread.detach()をmyに置き換えるthread.join()は、スレッドが完了した後にローカル変数が破棄されることを保証します.この場合、元のスレッドはそのライフサイクルで何もしていないため、独立したスレッドで関数を実行するのは収益が小さくなりますが、実際のプログラミングでは、元のスレッドには独自の仕事があります.複数のサブスレッドが起動して役に立つ作業を行い、スレッドの終了を待つかのいずれかです.
join()は単純で乱暴なスレッドの完了を待つか、待たないかです.待機中のスレッドをより柔軟に制御する必要がある場合は、たとえば、スレッドが終了しているかどうかを確認したり、しばらく待つだけ(時間を超えるとタイムアウトと判定されます).これを行うには、条件変数や期待(futures)など、他のメカニズムを使用して完了する必要があります.join()の動作を呼び出し、std::threadオブジェクトが完了したスレッドに関連付けられなくなるように、スレッド関連のストレージ部分もクリーンアップします.これは、1つのスレッドに対して1回しかjoin()を使用できないことを意味する.join()が既に使用されている場合、std::threadオブジェクトは再び追加できません.joinable()を使用するとfalseが返されます.
パラメータをスレッドに渡す
コードは、f(3,「hello」)を呼び出すスレッドを作成します.なお、関数fにはstd::stringオブジェクトを2番目のパラメータとして必要とするが、ここでは文字列の文字面値、すなわちchar const*タイプが使用される.その後、スレッドのコンテキストで文字値のstd::stringオブジェクトへの変換が完了します.
コンストラクション関数は、関数が期待するパラメータタイプを無視し、提供された変数を盲目的にコピーすることに注意してください.
例:
この場合、buffer②はポインタ変数であり、ローカル変数を指し、ローカル変数はbufferを介して新しいスレッド②に渡される.さらに、関数は、文字値がstd::stringオブジェクトに変換される前にクラッシュ(oops)し、未定義の動作を引き起こす可能性が高い.また,暗黙変換に依存してワード値を関数に変換するのに期待されるstd::stringオブジェクトを求めたが,std::threadのコンストラクション関数が与えられた変数をコピーするため,所望のタイプに変換されていない文字列ワード値のみをコピーした.解決策は、std::threadコンストラクション関数に渡す前に、フォント値をstd::stringオブジェクトに変換することです.
たとえば、2つ目は、リファレンスを渡すことが望ましいが、オブジェクト全体がコピーされているという逆の状況に遭遇することもあります.スレッドが参照伝達データ構造を更新すると、次のようなことが起こります.
反発の使用
C++ではstd::mutexをインスタンス化して反発量を作成し、メンバー関数lock()を呼び出してロックし、unlock()をロック解除します.ただし、実際にメンバー関数を直接呼び出すことは推奨されません.メンバー関数を呼び出すことは、各関数の出口でunlock()を呼び出すことを覚えておく必要があります.例外も含まれます.C++標準ライブラリは、相互反発量にRAII構文のテンプレートクラスstd::lock_guardは、構造時にロックされた反発量を提供し、分析時にロックを解除し、ロックされた反発量が常に正確にロックされることを保証する.次のプログラムリストでは、マルチスレッドプログラムでstd::mutexで構築されたstd::lock_を使用する方法を示します.guardインスタンスは、リストにアクセスして保護します.std::mutexとstd::lock_guardはすべてヘッダファイルに宣言されます.
メンバー関数の1つがデータを保護するポインタまたは参照を返すと、データの保護が破壊されます.相互反発ロックによって制限されることなく、保護されたデータにアクセス(変更可能)できるポインタまたはリファレンス.反発量保護データは、インタフェースの設計に非常に慎重である必要があります.反発量が保護データへのアクセスをロックし、バックドアを残さないようにします.
ロックの使用
おもちゃがあると思ってみてください.このおもちゃは2つの部分からなっていて、この2つの部分を手に入れなければ、游ぶことができません.例えば、おもちゃのドラムは、ハンマーとドラムが必要です.今は2人の子供がいます.彼らはこのおもちゃが大好きです.その中の一人の子供がドラムとハンマーを手に入れたとき、思う存分遊ぶことができます.もう一人の子供が遊びたいときは、もう一人の子供が遊び終わるのを待たなければなりません.もう一度考えてみると、ドラムとハンマーは別のおもちゃ箱に入れられ、二人の子供は同じ時間にドラムを叩きたいと思っています.その後、彼らはおもちゃ箱の中にこのドラムを探しに行きました.そのうちの1つはドラムを見つけ、もう1つはドラムハンマーを見つけた.今問題が来ました.一人の子供がもう一人を先に遊ばせることを決めない限り、彼は自分の部分をもう一人の子供にあげることができます.しかし、彼らが自分のすべての部分を握りしめて与えないと、このドラムは誰も游ぶことができません.
幸いなことに、C++標準ライブラリはこの問題を解決する方法があります.std::lock--複数(2つ以上)の反発量を一度にロックすることができ、副作用(デッドロックリスク)はありません.
デッドロックを回避するにはどうすればいいですか?ネストされたロックを回避する最初の推奨事項は、スレッドがロックを取得した場合、2番目のロックを取得しないでください.この提案を堅持できれば、スレッドごとにロックが1つしか持たないため、ロックにデッドロックは発生しません. 固定順序を使用してロックを取得ハード条件で2つ以上(2つを含む)のロックを取得する必要があり、std::lock単独で操作して取得することはできません.では、各スレッドにおいて、固定された順序でそれらを取得することが望ましい(ロック).
同期待機
もしあなたが旅行していて、夜間運行の列車に乗っているとします.夜間、どのようにして正しい駅で降りますか?1つの方法は、一晩中目が覚めて、どの駅に気づいたのかということです.これにより、到着するサイトを見逃すことはありませんが、疲れてしまいます.また、スケジュールを見て、列車が目的地に着く時間を見積もって、少し早い時間にアラームを設置して、安心して寝ることができます.この方法もいいですね.降りる駅を逃していませんが、汽車が遅れたとき、早く起こされます.もちろん、目覚まし時計のバッテリーも電気が切れ、駅を通過する可能性があります.理想的な方法は、朝でも晩でも、汽車が駅に着いたとき、誰かがあなたを呼び覚ますことができればいいということです.
C++標準ライブラリは条件変数に対して2つの実装がある:std::condition_variableとstd::condition_variable_any.両方のインプリメンテーションは、ヘッダファイルの宣言に含まれます.両方とも1つの反発量と一緒に動作する必要があります(反発量は同期のためです).前者はstd::mutexとのみ動作し、後者は最低基準を満たす反発量とともに動作し、_を加えることができる.anyの接尾辞.なぜならstd::condition_variable_anyはより汎用的であり、ボリューム、パフォーマンス、およびシステムリソースの使用から追加のオーバーヘッドが発生する可能性があるため、std::condition_variableは一般的に第一選択のタイプとして、柔軟性にハードな要求がある場合、std::condition_を考慮します.variable_any.
C++では従来バージョンではスレッドはサポートされていません.pthreadなどの使用スレッドが必要です.C++自身のスレッドを使用できれば,プログラムを統一して簡潔にすることができる.
ヘッダファイル
以上がc++11のスレッド部分です.pthreadとc++threadについては多くの議論があるが、プラットフォームをまたぐc++threadにとっては、より標準的なようだ.
この本はc++11のスレッドを学ぶことをお勧めします.https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details
hello worldスレッド
threadクラスの単純な認識
#include
using namespace std;
//
void hello()
{
printf("%s", "hello
");
}
// thread , 。 ,
int main()
{
thread t(hello);
// join
t.join();
}
joinを除いて終了を待つには、detachを使用してスレッドの終了を待たないことができます.
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. :
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2.
} // 3.
この例では、スレッドの終了を待たない(detach()②)と決定しているので、oops()関数の実行が完了した場合③は、新しいスレッドの関数がまだ実行されている可能性があります.スレッドがまだ実行されている場合はdo_が呼び出されますsomething(i)関数①は、破棄された変数にアクセスします.単一スレッドプログラムのように、関数が完了した後もローカル変数のポインタまたは参照を保持し続けることができます.もちろん、これは決して良いアイデアではありません.このような状況が発生すると、エラーは明らかではなく、マルチスレッドがエラーしやすくなります.
スレッドの終了を待つにはどうすればいいですか?
スレッドを待つ必要がある場合は、関連するstd::threadインスタンスはjoin()を使用する必要があります.インベントリ2.1でmy_thread.detach()をmyに置き換えるthread.join()は、スレッドが完了した後にローカル変数が破棄されることを保証します.この場合、元のスレッドはそのライフサイクルで何もしていないため、独立したスレッドで関数を実行するのは収益が小さくなりますが、実際のプログラミングでは、元のスレッドには独自の仕事があります.複数のサブスレッドが起動して役に立つ作業を行い、スレッドの終了を待つかのいずれかです.
join()は単純で乱暴なスレッドの完了を待つか、待たないかです.待機中のスレッドをより柔軟に制御する必要がある場合は、たとえば、スレッドが終了しているかどうかを確認したり、しばらく待つだけ(時間を超えるとタイムアウトと判定されます).これを行うには、条件変数や期待(futures)など、他のメカニズムを使用して完了する必要があります.join()の動作を呼び出し、std::threadオブジェクトが完了したスレッドに関連付けられなくなるように、スレッド関連のストレージ部分もクリーンアップします.これは、1つのスレッドに対して1回しかjoin()を使用できないことを意味する.join()が既に使用されている場合、std::threadオブジェクトは再び追加できません.joinable()を使用するとfalseが返されます.
パラメータをスレッドに渡す
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
コードは、f(3,「hello」)を呼び出すスレッドを作成します.なお、関数fにはstd::stringオブジェクトを2番目のパラメータとして必要とするが、ここでは文字列の文字面値、すなわちchar const*タイプが使用される.その後、スレッドのコンテキストで文字値のstd::stringオブジェクトへの変換が完了します.
コンストラクション関数は、関数が期待するパラメータタイプを無視し、提供された変数を盲目的にコピーすることに注意してください.
例:
void f(int i,std::string const& s);
void oops(int some_param)
{
char buffer[1024]; // 1
sprintf(buffer, "%i",some_param);
std::thread t(f,3,buffer); // 2
t.detach();
}
この場合、buffer②はポインタ変数であり、ローカル変数を指し、ローカル変数はbufferを介して新しいスレッド②に渡される.さらに、関数は、文字値がstd::stringオブジェクトに変換される前にクラッシュ(oops)し、未定義の動作を引き起こす可能性が高い.また,暗黙変換に依存してワード値を関数に変換するのに期待されるstd::stringオブジェクトを求めたが,std::threadのコンストラクション関数が与えられた変数をコピーするため,所望のタイプに変換されていない文字列ワード値のみをコピーした.解決策は、std::threadコンストラクション関数に渡す前に、フォント値をstd::stringオブジェクトに変換することです.
void f(int i,std::string const& s);
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer,"%i",some_param);
std::thread t(f,3,std::string(buffer)); // std::string,
t.detach();
}
たとえば、2つ目は、リファレンスを渡すことが望ましいが、オブジェクト全体がコピーされているという逆の状況に遭遇することもあります.スレッドが参照伝達データ構造を更新すると、次のようなことが起こります.
void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget,w,data); // 2
display_status();
t.join();
process_widget_data(data); // 3
}
反発の使用
C++ではstd::mutexをインスタンス化して反発量を作成し、メンバー関数lock()を呼び出してロックし、unlock()をロック解除します.ただし、実際にメンバー関数を直接呼び出すことは推奨されません.メンバー関数を呼び出すことは、各関数の出口でunlock()を呼び出すことを覚えておく必要があります.例外も含まれます.C++標準ライブラリは、相互反発量にRAII構文のテンプレートクラスstd::lock_guardは、構造時にロックされた反発量を提供し、分析時にロックを解除し、ロックされた反発量が常に正確にロックされることを保証する.次のプログラムリストでは、マルチスレッドプログラムでstd::mutexで構築されたstd::lock_を使用する方法を示します.guardインスタンスは、リストにアクセスして保護します.std::mutexとstd::lock_guardはすべてヘッダファイルに宣言されます.
#include
#include
#include
std::list some_list; // 1
std::mutex some_mutex; // 2
void add_to_list(int new_value)
{
std::lock_guard<:mutex> guard(some_mutex); // 3
some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
std::lock_guard<:mutex> guard(some_mutex); // 4
return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end();
}
メンバー関数の1つがデータを保護するポインタまたは参照を返すと、データの保護が破壊されます.相互反発ロックによって制限されることなく、保護されたデータにアクセス(変更可能)できるポインタまたはリファレンス.反発量保護データは、インタフェースの設計に非常に慎重である必要があります.反発量が保護データへのアクセスをロックし、バックドアを残さないようにします.
ロックの使用
おもちゃがあると思ってみてください.このおもちゃは2つの部分からなっていて、この2つの部分を手に入れなければ、游ぶことができません.例えば、おもちゃのドラムは、ハンマーとドラムが必要です.今は2人の子供がいます.彼らはこのおもちゃが大好きです.その中の一人の子供がドラムとハンマーを手に入れたとき、思う存分遊ぶことができます.もう一人の子供が遊びたいときは、もう一人の子供が遊び終わるのを待たなければなりません.もう一度考えてみると、ドラムとハンマーは別のおもちゃ箱に入れられ、二人の子供は同じ時間にドラムを叩きたいと思っています.その後、彼らはおもちゃ箱の中にこのドラムを探しに行きました.そのうちの1つはドラムを見つけ、もう1つはドラムハンマーを見つけた.今問題が来ました.一人の子供がもう一人を先に遊ばせることを決めない限り、彼は自分の部分をもう一人の子供にあげることができます.しかし、彼らが自分のすべての部分を握りしめて与えないと、このドラムは誰も游ぶことができません.
幸いなことに、C++標準ライブラリはこの問題を解決する方法があります.std::lock--複数(2つ以上)の反発量を一度にロックすることができ、副作用(デッドロックリスク)はありません.
デッドロックを回避するにはどうすればいいですか?
同期待機
もしあなたが旅行していて、夜間運行の列車に乗っているとします.夜間、どのようにして正しい駅で降りますか?1つの方法は、一晩中目が覚めて、どの駅に気づいたのかということです.これにより、到着するサイトを見逃すことはありませんが、疲れてしまいます.また、スケジュールを見て、列車が目的地に着く時間を見積もって、少し早い時間にアラームを設置して、安心して寝ることができます.この方法もいいですね.降りる駅を逃していませんが、汽車が遅れたとき、早く起こされます.もちろん、目覚まし時計のバッテリーも電気が切れ、駅を通過する可能性があります.理想的な方法は、朝でも晩でも、汽車が駅に着いたとき、誰かがあなたを呼び覚ますことができればいいということです.
C++標準ライブラリは条件変数に対して2つの実装がある:std::condition_variableとstd::condition_variable_any.両方のインプリメンテーションは、ヘッダファイルの宣言に含まれます.両方とも1つの反発量と一緒に動作する必要があります(反発量は同期のためです).前者はstd::mutexとのみ動作し、後者は最低基準を満たす反発量とともに動作し、_を加えることができる.anyの接尾辞.なぜならstd::condition_variable_anyはより汎用的であり、ボリューム、パフォーマンス、およびシステムリソースの使用から追加のオーバーヘッドが発生する可能性があるため、std::condition_variableは一般的に第一選択のタイプとして、柔軟性にハードな要求がある場合、std::condition_を考慮します.variable_any.