[Win 32]マルチスレッドとスレッド同期
5292 ワード
このブログはCSDNブロガーのzishikonghuaによって作成されました。著作権はzishikong huangに帰属します。転載は出所を明記してください。http://blog.csdn.net/zuishikonghuan/article/details/48208357
マルチスレッド:一つのプロセスが作成されると、デフォルトではシステムがメインスレッドを作成します。(Native APIを使って作成したスレッドにはメインスレッドがありません。空です。自分でメインスレッドを作成しなければなりません。)アプリケーションは自分でスレッドを作成します。また、以前書いた「DLL注入技術」もあります。リモートで他のプロセスにスレッドを作成します。そして、リモートスレッドをロードさせます。私たちのdll。
システムはどうやってマルチスレッドを実現しますか?実際には、単一CPUの単一コアデバイスでは、決定された時点でメモリ内の一つの命令しか実行できません。いわゆる「マルチタスク占領型オペレーティングシステム」とは、CPUを「時間帯」に分割してスレッドごとに割り当てたもので、システムのタスクスケジュールプログラムは、時間帯に応じてスレッドコンテキストとプロセスコンテキスト(スレッドのレジスタやステータスなどの情報が文脈に格納されている)を切り替えます。この時間帯は短すぎてはいけません。さもなくばスレッドはまだ何をしていませんか?切り替えて行って、効率を浪費して、更に長すぎることができなくて、さもなくばユーザーはプログラムが同時に運行するのではないと感じます。
スレッドを作成します。標準Win 32 APIはCreateThreadです。
CreateThread関数:
第2のパラメータ:スレッドスタックの空間サイズ。0は標準サイズを使用しています。
第3のパラメータ:新しいスレッドが実行するスレッド関数アドレス。
スレッド関数のプロトタイプ:
4番目のパラメータ:スレッド関数に送るパラメータ。
5つ目のパラメータ:0はスレッド作成後に直接運転します。CREATE_SUSPENDEDはスレッドの作成後に運転を停止し、Resume Threadを呼び出してスレッドを運転するという意味です。
6番目のパラメータ:スレッドのIDを返します。
戻り値:新しいスレッドのハンドルを返しました。失敗してNULLに戻りました。
特に説明します。CreateThread後、操作スレッドが必要でなければ、直接Close Handleはこのスレッドのハンドルを落としてもいいです。このスレッドのハンドルを閉じても、スレッドの運転に影響がありません。
についてビギンスreadex関数:
他にもう一つの_がありますBeginnthreadex関数は、スレッドを作成するためのものです。
多くの資料が「関数を使うべきで、CreateThread関数を使わないでください」と繰り返し強調していますが、その通りです。
MSDNでは、
A thread in an executable that cals the C run-time library(CRT)shuld use the_ビギンスreadex and_endthreadex functions for thread management rather than CreateThread and Exit Thread;this requires the use of the multihreaded version of the CRT.If a thread created using CreateThread cars the CRT,the CRT may terminate the process in low-memory contions.
なぜならBeginnthreadexは、C/C++の実行時関数をマルチスレッドにサポートするために設計されました。まず、C/C++の運転時関数の環境を構築し、その後、内部からCreateThreadを呼び出しました。したがって、C/C++の運転時関数が必要でない場合は、CreateThreadを使用して、_を使用しないことを推奨します。Beginnthreadex!なぜならBeginnthreadexはCPUとメモリ資源の浪費をもたらします。C/C++の運転時関数を使用しないと、プログラムの効率が要求される条件の下で、CreateThreadを使用するべきです。
実は、ほとんどのC/C++の実行時関数はシステムのAPIを呼び出しています。
スレッドを閉じるには:
ユーザモード(Win 32サブシステム)でスレッドを閉じる正しいやり方は、スレッドを自分で返すことであり、強制終了スレッドは好ましくない。
カーネルモードでは、他の作業も必要です。
スレッド同期:
複数のスレッドを作成した場合、スレッドが同じリソースにアクセスすれば、結果はどうなりますか?
グローバル変数xを仮定して、2つのスレッドa,bを作成した。aとbはすべてxを読み书きます。结果は分かりません。なぜですか?システムはスレッドを随時スケジュールしていますが、2つの困難な原因があります。
1です。変数に対する操作は、直接メモリにアクセスするのではなく、メモリユニットをレジスタに読み込んでレジスタを修正し、レジスタのデータをメモリに書き込みます。
スレッドaがxをレジスタに読み込むと、CPUをbにセットし、bがxを変更し、aに戻り、メモリが変わったことが分かりません。レジスタを変更してメモリに書き込みます。これでスレッドbが無駄になります。これからもプログラムに問題があるかもしれません。
信用しないなら、逆にあなたのプログラムを編集して、コードを編集してみてもいいです。
2です。CPUが読み書きメモリを直接読んだり書いたりするのではなく、CPUが連続読み書きメモリの効率を高めるために、「キャッシュ行」を導入しました。メモリの一部をキャッシュ行に読み出してからメモリに書き込むので、スレッドスケジュールがキャッシュ行に読み込まれた直後にスレッドコンテキストを切り替えると上記の問題と同じです。
したがって,原子アクセスを実現するために「スレッド同期」を行う必要がある。
ユーザモードでよく見られるスレッド同期の方法は、イベント(Event)、相互反発体(Mutex)、信号量(Semaphore)などです。
その中で最もよく使われているのは相互反発体です。
相互反発体について:スレッドが相互反発体を取得した場合、他のスレッドは取得できなくなり、一方のスレッドは同時に一つのスレッドにしか獲得できません。他のスレッドを取得しようとすると、相互反発体のリリースを待っています。リリース後、もう一つのスレッドは相互反発体を取得します。
このように、スレッドが同じリソースにアクセスする時に、相互反発体を取得し、アクセスが完了したら、上記の問題を回避できます。これがスレッド同期です。
相互反発体CreateMutexを作成:
パラメータ2:占有されていますか?
パラメータ3:名前付き
リターン値:反発体のハンドルを返すことに成功しました。
反発体ReleaseMutexを放出する:
相互反発体WaitForSingleObjectを得る:
パラメータ2:超時間間隔、ミリ秒単位。非ゼロ値を指定すると、関数はオブジェクトが終了状態または到着時間間隔になるまで待機します。INFINITEはオブジェクトが終了するまで待つという意味です。
スレッドの完了を待つ:WaitForSingleObjectを使ってスレッドのハンドルを待てばいいです。
例:
ここではスレッドが二つしかないので、スレッドごとに一回しか操作しないので、効果がよくないです。forループを使って、二つのスレッドをそれぞれ異なる内容で何回も出力してもいいです。スレッドをかけない同期効果は明らかです。
マルチスレッド:一つのプロセスが作成されると、デフォルトではシステムがメインスレッドを作成します。(Native APIを使って作成したスレッドにはメインスレッドがありません。空です。自分でメインスレッドを作成しなければなりません。)アプリケーションは自分でスレッドを作成します。また、以前書いた「DLL注入技術」もあります。リモートで他のプロセスにスレッドを作成します。そして、リモートスレッドをロードさせます。私たちのdll。
システムはどうやってマルチスレッドを実現しますか?実際には、単一CPUの単一コアデバイスでは、決定された時点でメモリ内の一つの命令しか実行できません。いわゆる「マルチタスク占領型オペレーティングシステム」とは、CPUを「時間帯」に分割してスレッドごとに割り当てたもので、システムのタスクスケジュールプログラムは、時間帯に応じてスレッドコンテキストとプロセスコンテキスト(スレッドのレジスタやステータスなどの情報が文脈に格納されている)を切り替えます。この時間帯は短すぎてはいけません。さもなくばスレッドはまだ何をしていませんか?切り替えて行って、効率を浪費して、更に長すぎることができなくて、さもなくばユーザーはプログラムが同時に運行するのではないと感じます。
スレッドを作成します。標準Win 32 APIはCreateThreadです。
CreateThread関数:
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
第1のパラメータ:スレッドカーネルオブジェクトのセキュリティ属性は、一般的にNULLにセットされ、デフォルトの設定が使用されます。第2のパラメータ:スレッドスタックの空間サイズ。0は標準サイズを使用しています。
第3のパラメータ:新しいスレッドが実行するスレッド関数アドレス。
スレッド関数のプロトタイプ:
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
lp Parameeter:CreateThreadを通じて人を伝える4番目のパラメータです。4番目のパラメータ:スレッド関数に送るパラメータ。
5つ目のパラメータ:0はスレッド作成後に直接運転します。CREATE_SUSPENDEDはスレッドの作成後に運転を停止し、Resume Threadを呼び出してスレッドを運転するという意味です。
6番目のパラメータ:スレッドのIDを返します。
戻り値:新しいスレッドのハンドルを返しました。失敗してNULLに戻りました。
特に説明します。CreateThread後、操作スレッドが必要でなければ、直接Close Handleはこのスレッドのハンドルを落としてもいいです。このスレッドのハンドルを閉じても、スレッドの運転に影響がありません。
についてビギンスreadex関数:
他にもう一つの_がありますBeginnthreadex関数は、スレッドを作成するためのものです。
多くの資料が「関数を使うべきで、CreateThread関数を使わないでください」と繰り返し強調していますが、その通りです。
MSDNでは、
A thread in an executable that cals the C run-time library(CRT)shuld use the_ビギンスreadex and_endthreadex functions for thread management rather than CreateThread and Exit Thread;this requires the use of the multihreaded version of the CRT.If a thread created using CreateThread cars the CRT,the CRT may terminate the process in low-memory contions.
なぜならBeginnthreadexは、C/C++の実行時関数をマルチスレッドにサポートするために設計されました。まず、C/C++の運転時関数の環境を構築し、その後、内部からCreateThreadを呼び出しました。したがって、C/C++の運転時関数が必要でない場合は、CreateThreadを使用して、_を使用しないことを推奨します。Beginnthreadex!なぜならBeginnthreadexはCPUとメモリ資源の浪費をもたらします。C/C++の運転時関数を使用しないと、プログラムの効率が要求される条件の下で、CreateThreadを使用するべきです。
実は、ほとんどのC/C++の実行時関数はシステムのAPIを呼び出しています。
スレッドを閉じるには:
ユーザモード(Win 32サブシステム)でスレッドを閉じる正しいやり方は、スレッドを自分で返すことであり、強制終了スレッドは好ましくない。
カーネルモードでは、他の作業も必要です。
スレッド同期:
複数のスレッドを作成した場合、スレッドが同じリソースにアクセスすれば、結果はどうなりますか?
グローバル変数xを仮定して、2つのスレッドa,bを作成した。aとbはすべてxを読み书きます。结果は分かりません。なぜですか?システムはスレッドを随時スケジュールしていますが、2つの困難な原因があります。
1です。変数に対する操作は、直接メモリにアクセスするのではなく、メモリユニットをレジスタに読み込んでレジスタを修正し、レジスタのデータをメモリに書き込みます。
スレッドaがxをレジスタに読み込むと、CPUをbにセットし、bがxを変更し、aに戻り、メモリが変わったことが分かりません。レジスタを変更してメモリに書き込みます。これでスレッドbが無駄になります。これからもプログラムに問題があるかもしれません。
信用しないなら、逆にあなたのプログラムを編集して、コードを編集してみてもいいです。
2です。CPUが読み書きメモリを直接読んだり書いたりするのではなく、CPUが連続読み書きメモリの効率を高めるために、「キャッシュ行」を導入しました。メモリの一部をキャッシュ行に読み出してからメモリに書き込むので、スレッドスケジュールがキャッシュ行に読み込まれた直後にスレッドコンテキストを切り替えると上記の問題と同じです。
したがって,原子アクセスを実現するために「スレッド同期」を行う必要がある。
ユーザモードでよく見られるスレッド同期の方法は、イベント(Event)、相互反発体(Mutex)、信号量(Semaphore)などです。
その中で最もよく使われているのは相互反発体です。
相互反発体について:スレッドが相互反発体を取得した場合、他のスレッドは取得できなくなり、一方のスレッドは同時に一つのスレッドにしか獲得できません。他のスレッドを取得しようとすると、相互反発体のリリースを待っています。リリース後、もう一つのスレッドは相互反発体を取得します。
このように、スレッドが同じリソースにアクセスする時に、相互反発体を取得し、アクセスが完了したら、上記の問題を回避できます。これがスレッド同期です。
相互反発体CreateMutexを作成:
HANDLE WINAPI CreateMutex(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_ LPCTSTR lpName
);
パラメータ1:セキュリティ属性、NULLはデフォルトです。パラメータ2:占有されていますか?
パラメータ3:名前付き
リターン値:反発体のハンドルを返すことに成功しました。
反発体ReleaseMutexを放出する:
BOOL WINAPI ReleaseMutex(
_In_ HANDLE hMutex
);
パラメータ:反発体ハンドル相互反発体WaitForSingleObjectを得る:
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);
パラメータ1:オブジェクトのハンドルを待つ(お互いに取り込むハンドル)パラメータ2:超時間間隔、ミリ秒単位。非ゼロ値を指定すると、関数はオブジェクトが終了状態または到着時間間隔になるまで待機します。INFINITEはオブジェクトが終了するまで待つという意味です。
スレッドの完了を待つ:WaitForSingleObjectを使ってスレッドのハンドルを待てばいいです。
例:
ここではスレッドが二つしかないので、スレッドごとに一回しか操作しないので、効果がよくないです。forループを使って、二つのスレッドをそれぞれ異なる内容で何回も出力してもいいです。スレッドをかけない同期効果は明らかです。
#include
#include
int x = 0;
DWORD WINAPI ThreadProc(LPVOID lpParameter){
//
HANDLE* pMutex = (HANDLE*)lpParameter;
// ,
WaitForSingleObject(*pMutex, INFINITE);
//
x++;
//
ReleaseMutex(*pMutex);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
//
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("MyMutex1"));
//
HANDLE t1 = CreateThread(NULL, 0, ThreadProc, &hMutex, 0, NULL);
HANDLE t2 = CreateThread(NULL, 0, ThreadProc, &hMutex, 0, NULL);
//
WaitForSingleObject(t1, INFINITE);
WaitForSingleObject(t2, INFINITE);
// ,
CloseHandle(hMutex);
printf("%d", x);
getchar();
return 0;
}