#pragma twice

KAB-studio > プログラミング > #pragma twice > 286 Version 14.19 クリティカルセクションを使ってみる!

#pragma twice 286 Version 14.19 クリティカルセクションを使ってみる!

前のページへ 表紙・目次へ 次のページへ

 Version 14.19
クリティカルセクションを使ってみる!

「というわけで今回は、前回の続き、クリティカルセクションについてもう
少し詳しく見てみます」
『ただコード見ただけだったもんね』
「そうだね、まずはそのコードを追うところから。まず、クリティカル
セクション用同期オブジェクトも、他の同期オブジェクトと同じように、
グローバル変数で持つことにします」

// クリティカルセクション用同期オブジェクト。
CRITICAL_SECTION g_stCriticalSection;

『でもハンドルじゃないよ』
「そう、クリティカルセクション用の同期オブジェクトはハンドルじゃない
んです」
『なんかここから違う……』
「次に、この変数の初期化と解放」

// IncrementThread() を別スレッドとして呼び出す関数。
// この関数をボタンのイベントハンドラとかで呼んでください。
void CallThread()
{
    // クリティカルセクションを初期化します。
    InitializeCriticalSection( &g_stCriticalSection );

//  スレッド呼び出しと待機処理。

    // クリティカルセクションを削除します。
    DeleteCriticalSection( &g_stCriticalSection );
}

「 API の InitializeCriticalSection() で初期化して、 
DeleteCriticalSection() で解放します」
『これもハンドル受け取ったりしないんだね……』
「それに、名前も渡してないでしょ」
『ホントだ! 同期オブジェクトって言ったら名前で識別するもんなのに』
「クリティカルセクションはプロセスの中だけだから」
『どゆこと?』
「他の同期オブジェクトは複数のプロセスからアクセスできるわけだけど、
そのためには名前が必要でしょ。ハンドルを HANDLE 型の変数に入れても、
他のプロセスからは見られないんだから」
『あ、そっか、メモリ別だから……クリティカルセクションは同じプロセス
の中でしか使えない、だから逆にメモリは全部共有……』
「 g_stCriticalSection はプログラムのどこからでもアクセスできるから
ね。まぁ、この初期化と削除は特にたいしたことはしてないから、次の一番
重要な部分を見てみます」

// 別スレッドで呼び出される、 g_iData を増やし続ける関数。
void __cdecl IncrementThread( void *p_p )
{
    TRACE( "スレッド開始。\n" );
    for( int iF1 = 0; iF1 < 10; ++iF1 )
    {
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

        int i = g_iData;
        // 0.1 秒待ちます。
        Sleep( 100 );
        g_iData = i + 1;
        TRACE( "%d\n", g_iData );

        // クリティカルセクションのロックを解除します。
        LeaveCriticalSection( &g_stCriticalSection );
    }
    TRACE( "スレッド終了。\n" );
}

『これって、前の時に比べて』

        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

『と』

        // クリティカルセクションのロックを解除します。
        LeaveCriticalSection( &g_stCriticalSection );

『が追加されたんだよね』
「そう。このふたつはペアで使います。まず、クリティカルセクションには
【コードを排他する】という概念があります」
『コードを排他?』
「このふたつで挟まれた範囲のコード、つまり」

        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );
// ↓ここから
        int i = g_iData;
        // 0.1 秒待ちます。
        Sleep( 100 );
        g_iData = i + 1;
        TRACE( "%d\n", g_iData );
// ↑ここまで
        // クリティカルセクションのロックを解除します。
        LeaveCriticalSection( &g_stCriticalSection );

「は、クリティカルセクションによって排他されます」
『排他……ってことは、ひとつがアクセスしてたら、他がアクセスできなく
なる、ってこと?』
「そう、 Version 14.03 ( No.270 ) のファイルの排他処理とかと同じ意味
の排他。このふたつの API 、 EnterCriticalSection() と 
LeaveCriticalSection() で囲まれた間のコードは、たったひとつのスレッド
だけが実行できます」
『たったひとつのスレッドだけ?』
「そう。たとえばスレッド1がこの処理を実行しようとしたとします」

// ※↓スレッド1進行中↓
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

「スレッド1が EnterCriticalSection() を呼び出すと、ロックがかかりま
す」

        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );
// ※↓スレッド1進行中↓
// ロックがかかりました。

「ここに、スレッド2が来たとします」

// ※↓スレッド2進行中↓
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

// ※↓スレッド1進行中↓
        int i = g_iData;
        // 0.1 秒待ちます。
        Sleep( 100 );

「ところが、スレッド1がロックを掛けているのでスレッド2はここで足止
めとなります」

// ※↓スレッド2進行中↓
// ロックが掛かっているのでここで止まります。
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

        int i = g_iData;
        // 0.1 秒待ちます。
// ※↓スレッド1進行中↓
        Sleep( 100 );

「その間、スレッド1は処理をするわけです」
『スレッド2が入れない間に処理をしちゃうわけね』
「そういうこと。で、ロックが外れるのが、 LeaveCriticalSection() を呼
び出した時」

// ※↓スレッド2進行中↓
// ロックが解除されました。
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

        int i = g_iData;
        // 0.1 秒待ちます。
        Sleep( 100 );
        g_iData = i + 1;
        TRACE( "%d\n", g_iData );

        // クリティカルセクションのロックを解除します。
        LeaveCriticalSection( &g_stCriticalSection );
// ※↓スレッド1進行中↓
// ロックを解除しました。

「ロックが解除されると、スレッド2が入ることができます」

        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );
// ※↓スレッド2進行中↓
        int i = g_iData;
        // 0.1 秒待ちます。
        Sleep( 100 );
        g_iData = i + 1;
        TRACE( "%d\n", g_iData );

        // クリティカルセクションのロックを解除します。
        LeaveCriticalSection( &g_stCriticalSection );

// ※↓スレッド1進行中↓

「こうして、 EnterCriticalSection() と LeaveCriticalSection() で挟ん
だ間は、ひとつのスレッドだけが入れるようになるわけです」
『これが、コードに排他処理が掛かる、って意味なのね。……質問!』
「はい火美ちゃん」
『スレッド3があったら?』
「スレッド2と同じく待ちます」

// ※↓スレッド3進行中↓
// ロックが掛かっているのでここで止まります。
// ※↓スレッド2進行中↓
// ロックが掛かっているのでここで止まります。
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );

「ロックが解除されたら、先に来たスレッドだけ中に入って、その先に来た
スレッドがロックを掛けちゃうから、後に来た方はまた止まります」

// ※↓スレッド3進行中↓
// ロックが掛かっているのでここで止まります。
        // クリティカルセクションでロックを掛けます。
        EnterCriticalSection( &g_stCriticalSection );
// ※↓スレッド2進行中↓
// ロックがかかりました。

『なるほど、スレッドがいっぱいあっても結局入れるのはひとつだけなの
ね』
「そういうこと」

/*
    Preview Next Story!
*/
『ホントにだいぶ違うね、他の同期オブジェクトと』
「シグナルとか関係ないからわかりやすいでしょ」
『ってゆーか別物。だから逆にわかりにくいかも』
「確かに……まぁ、他の同期オブジェクトと同じ所もあるんだけど」
『たとえば?』
「というわけで次回」
< Version 14.20 クリティカルセクションと同期オブジェクト >
『につづく!』
「たとえばデッドロック起こせたりとか」
『げ』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。