プログラムを書こう!

実務や自作アプリ開発で習得した役に立つソフトウェア技術情報を発信するブログ

Win32APIでスレッド間の排他制御(クリティカルセクション)を行う。

この記事は2018年12月14日に投稿しました。

f:id:paveway:20190914064630j:plain

目次

  1. はじめに
  2. Win32APIでスレッド間の排他制御(クリティカルセクション)を行う
  3. おわりに

Win32/64 APIシステムプログラミング―32/64ビットの共存

Win32/64 APIシステムプログラミング―32/64ビットの共存

1. はじめに

こんにちは、iOSのエディタアプリPWEditorの開発者の二俣です。
今回は業務で使用しているWin32APIでスレッド間の排他制御(クリティカルセクション)を行う方法についてです。

目次へ

2. Win32APIでスレッド間の排他制御(クリティカルセクション)を行う

スレッド間の排他制御の方法はいろいろありますが、今回はWin32APIでスレッド間の排他制御(クリティカルセクション)を行う方法を紹介します。

クリティカルセクションによる排他制御の方法ですが、

クリティカルセクションオブジェクトを保持しているスレッドだけが処理を実行できる。

という方法になります。

使うAPIは次の4つになります。

1. クリティカルセクションの作成

書式
VOID InitializeCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection
);
引数

lpCriticalSection
クリティカルセクションオブジェクト

戻り値

なし

2. クリティカルセクションの保持

書式
VOID EnterCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection
);
引数

lpCriticalSection
クリティカルセクションオブジェクト

戻り値

なし

3. クリティカルセクションの解放

書式
VOID LeaveCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection
);
引数

lpCriticalSection
クリティカルセクションオブジェクト

戻り値

なし

4. クリティカルセクションの削除

書式
VOID DeleteCriticalSection(
    LPCRITICAL_SECTION lpCriticalSection
);
引数

lpCriticalSection
クリティカルセクションオブジェクト

戻り値

なし

実装例

グローバル変数でカウンタ(g_count)を用意します。
スレッド1、スレッド2でカウンタをそれぞれカウントアップ、カウントダウンして、その結果を表示します。

#include "stdafx.h"
#include <windows.h>

int g_count;       // 排他制御するオブジェクト
BOOL g_endThread1;  // スレッド1終了フラグ
BOOL g_endThread2;  // スレッド2終了フラグ

/**
 * @brief スレッド1処理
 *        カウンタをカウントアップして、その結果を表示します。
 *
 * @param [in] param クリティカルセクションオブジェクト
 * @return 処理結果
 */
DWORD WINAPI Thread1(LPVOID* param)
{
    // クリティカルセクションオブジェクトを保持し、排他制御を開始します。
    EnterCriticalSection((LPCRITICAL_SECTION)param);

    // 排他制御したい処理を記述します。
    // スレッド1ではg_countをカウントアップして、結果を表示します。
    while (g_count < 10)
    {
        printf("Thread1 count=%d\n", g_count);
        ++g_count;
        Sleep(13);
    }

    // クリティカルセクションオブジェクトを解放し、排他制御を終了します。
    LeaveCriticalSection((LPCRITICAL_SECTION)param);

    // スレッド終了フラグを設定します。
    g_endThread1 = TRUE;
    return 0;
}

/**
 * @brief スレッド2処理
 *        カウンタをカウントダウンして、その結果を表示します。
 *
 * @param [in] param クリティカルセクションオブジェクト
 * @return 処理結果
 */
DWORD WINAPI Thread2(LPVOID* param)
{
    // クリティカルセクションオブジェクトを保持し、排他制御を開始します。
    EnterCriticalSection((LPCRITICAL_SECTION)param);

    // 排他制御したい処理を記述します。
    // スレッド2ではg_countをカウントダウンして、結果を表示します。
    while (g_count > 0)
    {
        printf("Thread2 count=%d\n", g_count);
        --g_count;
        Sleep(7);
    }

    // クリティカルセクションオブジェクトを解放し、排他制御を終了します。
    LeaveCriticalSection((LPCRITICAL_SECTION)param);

    // スレッド終了フラグを設定します。
    g_endThread2 = TRUE;
    return 0;
}

/**
 * @brief メイン関数
 *
 * @param [in] argc コマンドライン引数の数
 * @param [in] argv[] コマンドライン引数
 * @return 処理結果
 */
int _tmain(int argc, _TCHAR* argv[])
{
    static CRITICAL_SECTION cs; // クリティカルセクションオブジェクト
    static HANDLE hThread1;        // スレッド1ハンドル
    static HANDLE hThread2;        // スレッド2ハンドル

    // グローバル変数を初期化します。
    g_count = 5;
    g_endThread1 = FALSE;
    g_endThread2 = FALSE;

    // クリティカルセクションを初期化します。
    InitializeCriticalSection(&cs);

    // スレッド1, スレッド2を生成し、開始します。
    hThread1 = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Thread1, (LPVOID)&cs, 0, NULL);
    hThread2 = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Thread2, (LPVOID)&cs, 0, NULL);

    // スレッド1, スレッド2の終了を待ちます。
    // 今回は処理を単純化するため、スレッド終了フラグが設定されるのをポーリングして待ちます。
    while (TRUE)
    {
        if (g_endThread1 && g_endThread2)
        {
            break;
        }

        Sleep(10);
    }

    // スレッドハンドルをクローズします。
    CloseHandle(hThread1);
    CloseHandle(hThread2);

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

    return 0;
}

f:id:paveway:20181213053001p:plain
実行結果(排他制御なし)

排他制御しないと、スレッド1とスレッド2が同時に実行されます。


f:id:paveway:20181213053334p:plain
実行結果(排他制御あり)

排他制御すると、スレッド1の処理が終わってから、スレッド2の処理が実行されます。

目次へ

3. おわりに

業務の通信処理で、機器のステータスリードしているスレッドとコマンドを送信するスレッドで、ステータスリードした結果を保持するグローバル変数を更新する際、排他制御するために使用しました。

猫でもわかるWindowsプログラミング 第4版 猫でもわかるシリーズ

猫でもわかるWindowsプログラミング 第4版 猫でもわかるシリーズ

紹介している一部の記事のコードはGitlabで公開しています。
興味のある方は覗いてみてください。

目次へ


私が勤務しているニューラルでは、主に組み込み系ソフトの開発を行っております。
弊社製品のハイブリッドOS Bi-OSは高い技術力を評価されており、特に制御系や通信系を得意としています。
私自身はiOSモバイルアプリウィンドウズアプリを得意としております。
ソフトウェア開発に関して相談などございましたら、お気軽にご連絡ください。

また一緒に働きたい技術者の方も随時募集中です。
興味がありましたらご連絡ください。

EMAIL : info-nr@newral.co.jp / m-futamata@newral.co.jp
TEL : 042-523-3663
FAX : 042-540-1688

目次へ