クラシックスレッド同期キーセグメントCS

6526 ワード

参照http://blog.csdn.net/morewindows/article/details/7442639
 
キーセグメントCRITICAL_SECTIONは全部で4つの関数で、使いやすいです.以下に、この4つの関数のプロトタイプと使用説明を示します.
 
関数機能かんすうきのう:初期化しょきか
関数のプロトタイプ:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
関数の説明:キーセグメント変数を定義した後、初期化する必要があります.
 
関数機能:破棄[かんすう:さくじょ]
関数のプロトタイプ:
void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
関数の説明:使い終わったら破棄してください.
 
関数機能:キー領域へ
関数のプロトタイプ:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
関数の説明:システムは各スレッドの反発が重要な領域に入ることを保証します.
 
関数機能:オフキー領域
関数のプロトタイプ:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
 
次に、古典的なマルチスレッド問題で2つのキー領域を設定します.1つは,メインスレッドがサブスレッドシーケンス番号をインクリメントする場合,もう1つは各サブスレッドが反発するアクセス出力グローバルリソースの場合である.コードは次のとおりです.
 
#include <windows.h>
#include <process.h>
#include <iostream>

using namespace std;

const int THREADNUM = 30;
volatile long number = 0;

CRITICAL_SECTION threadPM,threadCode;

unsigned int __stdcall threadFunc(PVOID pM) {
        int nThreadNum = *(int *)pPM;
	LeaveCriticalSection(&threadPM);
	Sleep(100);
	EnterCriticalSection(&threadCode);
	cout << nThreadNum  << endl;
	number++;
	Sleep(0);
	LeaveCriticalSection(&threadCode);
	return 0;
}

int main() {
	int num = 20;
	
	HANDLE handle[THREADNUM];

	InitializeCriticalSection(&threadCode);
	InitializeCriticalSection(&threadPM);

	number = 0;
	for(int i=0; i< THREADNUM; i++) {
		EnterCriticalSection(&threadPM);
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
	}
	
	WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); //     64
	Sleep(500);
	cout << "     " << number << endl;
	
	DeleteCriticalSection(&threadCode);
	DeleteCriticalSection(&threadPM);
	getchar();
	return 0;
}

 
 
出力結果:
经典线程同步 关键段CS
各サブスレッド間でグローバル変数への相互反発アクセスが可能になったが,プライマリスレッドとサブスレッド間の同期関係に問題が発生することを見出した.
 
これはなぜですか.
この謎を解くには、まずプログラムにブレークポイントを加えてプログラムの実行フローを表示することが最も直接的な方法です.ブレークポイント処理の概略は次のとおりです.
 
经典线程同步 关键段CS  
 
その後、F 5でデバッグを行います.通常、この2つのブレークポイントは順番に順番に実行されるはずですが、実際のデバッグではそうではありません.プライマリ・スレッドは最初のブレークポイントである
       EnterCriticalSection(&g_csThreadParameter);//サブスレッド番号キー領域へ
この文.これは、プライマリスレッドがこのキー領域に複数回アクセスできることを示しています.
 
 
キーセグメントの本質は、スレッド間の反発アクセスを保証することであり、同期アクセスではキーセグメントは使用できません.
 
 
キーセグメントCRITICAL_を先に見つけますSECTIONの定義でしょうWinBaseにありますhではRTL_と定義されるCRITICAL_SECTION.そしてRTL_CRITICAL_SECTIONはWinNTにあります.hでは、構造体であることが宣言されています.
typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
    LONGLockCount;
    LONGRecursionCount;
    HANDLEOwningThread; //from the thread's ClientId->UniqueThread
    HANDLELockSemaphore;
    DWORDSpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
各パラメータは次のように解釈されます.
最初のパラメータ:PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
デバッグ用です.
 
2番目のパラメータ:LONGLockCount;
-1に初期化され、nはn個のスレッドが待機していることを示す.
 
3番目のパラメータ:LONGRecorsionCount;  
このリソースに対して、キーセグメントの所有スレッドがキーセグメントを取得した回数を示します.最初は0です.
 
4番目のパラメータ:HANDLEOwningThread; 
つまり、このキーセグメントを持つスレッドハンドルであり、マイクロソフトはfrom the thread's ClientId->UniqueThreadとコメントしています.
 
5番目のパラメータ:HANDLELockSemaphore;
実際には自己リセットイベントです.
 
6番目のパラメータ:DWORDSpinCount;    
回転ロックの設定は、CPUのみで無視
 
 
 
この構造では、キーセグメントがキーセグメントを持つスレッドハンドル、すなわちキーセグメントに「スレッド所有権」の概念が記録されることがわかります.実際には、4番目のパラメータOwningThreadを使用して、キー領域へのアクセスが許可されているスレッドハンドルを記録します.このスレッドが再びアクセスされると、EnterCriticalSection()は、3番目のパラメータRecursionCountを更新して、スレッドがアクセスされた回数を記録し、すぐにスレッドをアクセスさせるように戻ります.
つまり、同じスレッドは繰り返し入力できますが、他のスレッドはキー領域がすべて解放されるまで待たなければなりません.
他のスレッドがEnterCriticalSection()を呼び出すと待機状態に切り替わり、スレッドの所有権を持つスレッドがLeaveCriticalSection()を呼び出して0にすると、キーセグメントが自動的に更新され、待機中のスレッドがスケジュール可能な状態に戻されます.
そのため、肝心な部分をホテルのルームカードにたとえて、EnterCriticalSection()を呼び出してルームカードを申請することができます.ルームカードを手に入れた後、自分はもちろん何度も部屋を出入りすることができます.LeaveCriticalSection()を呼び出してルームカードを渡す前に、他の人は自然にこの部屋に入ることができません.
この古典的なスレッド同期問題に戻ると、プライマリスレッドは「スレッド所有権」であるルームカードを持っているため、キーコード領域に繰り返しアクセスすることができ、サブスレッドがパラメータを受信する前にプライマリスレッドがこのパラメータを変更したことになります.したがって、キーセグメントはスレッド間の反発に使用できますが、同期には使用できません.
 
 
 
また、スレッドを待機状態に切り替えるオーバーヘッドが大きいため、Microsoftはキーのパフォーマンスを向上させるために回転ロックをキーにマージします.これにより、EnterCriticalSection()はまず1つの回転ロックでループし、スレッドを待機状態に切り替えるまでしばらく試します.次に、回転ロックを組み合わせたキーセグメント初期化関数を示します.
関数機能:キーセグメントを初期化し、回転数を設定する
関数のプロトタイプ:
BOOLInitializeCriticalSectionAndSpinCount(
  LPCRITICAL_SECTION lpCriticalSection,
  DWORD dwSpinCount);
関数の説明:回転数は通常4000に設定されます.
 
関数機能:キーセグメントの回転数を変更する
関数のプロトタイプ:
DWORDSetCriticalSectionSpinCount(
  LPCRITICAL_SECTION lpCriticalSection,
  DWORD dwSpinCount);
 
「Windowsコアプログラミング」第5版の第8章では、キーセグメントを使用するときに回転ロックを同時に使用することをお勧めします.これにより、パフォーマンスの向上に役立ちます.
ホストにプロセッサが1つしかない場合、回転ロックの設定は無効であることに注意してください.この関数を単一スレッドのマシンで呼び出すと、dwSpinCountパラメータは無視されるため、回数は常に0になります.シングルプロセッサのマシンでループ回数を設定するのは役に立たないため、1つのスレッドがループしている場合、リソースを占有するスレッドはリソースへのアクセス権を放棄する機会がありません.
難点は,dwSpinCountパラメータに伝達される値をどのように決定するかである.最適な性能を得るためには,性能に満足するまで種々の数値を試みることが最も簡単な方法である.プロセススタックを保護するために使用されるキーセグメントの回転数は約4000であり、これは私たちの参照値とすることができます.回転ロックの原理と定義は『回転ロックの解釈』を参照してください.
 
キー領域にアクセスできないスレッドは、常にシステムによって待機状態に切り替えられます.
 
最後に、重要なセグメントをまとめます.
1.キーセグメントの初期化、破棄、キー領域へのアクセス、およびオフの4つの関数.
2.キー・セグメントはスレッドの反発問題を解決できますが、「スレッド所有権」があるため、同期問題は解決できません.
3.キーセグメントは回転ロックと組み合わせて使用することを推奨します.