小さい頃はエラ呼吸

いつのまにやら肺で呼吸をしています。


VC++で作るマルチスレッドと排他制御のサンプルプログラム

はじめに

VC++でマルチスレッドプログラミングのサンプルコードを書いてみました。
以下のページに書かれているサンプルプログラムがVS2013にコピーペーストするだけで実行できて、とても参考になりました。

マルチスレッドプログラミングマルチスレッドプログラミング

スレッドの作成

スレッドの作成は、_beginthreadex関数を使います。

hThread[0] = (HANDLE)_beginthreadex(NULL, stackSize, IncrementCounter, NULL, 0, NULL);
第1引数 start_address 新規スレッドの実行を起動するルーチンの開始アドレス。
第2引数 stack_size 新規スレッドのスタック サイズまたは 0。
第3引数 arglist 新規スレッドに渡される引数リストまたは NULL。
第4引数 security SECURITY_ATTRIBUTES 構造体へのポインタ。
第5引数 initflag 新規スレッドの初期状態 (実行中は 0、一時停止中は CREATE_SUSPENDED)。
第6引数 thrdaddr スレッド識別子を受け取る 32 ビット変数へのポインタ。

参考にしたページでは、_beginthreadを使っていましたが、windowsは_beginthreadex推奨みたいなので、こちらを使いました。

スレッドを調べまわって分かったことをメモ - かせいさんとこスレッドを調べまわって分かったことをメモ - かせいさんとこ

解説

f:id:replication:20150301115347p:plain
以下のコードは、2つのスレッドを作成しています。

  • スレッド1 変数Aと変数Bをそれぞれカウントアップしてきます。
  • スレッド2 変数Aと変数Bのカウンタをゼロにリセットします。

このプログラムを実行すると、以下のような表示になります。
f:id:replication:20150228192454p:plain
スレッド1は変数Aを0〜9まで1つずつカウントアップできているように見えます。一方、変数Bも同じようにカウントアップしているはずなのですが、ゼロ(0)で表示されています。
これはスレッド1がカウントアップしている最中にスレッド2が変数Bを書き換えているために起こります。
変数AはWaitForSingleObjectを使い、他のスレッドを待たせているため割り込みが発生せずに、カウントアップができています。

サンプルコード
#include "stdafx.h"
#include <windows.h>
#include <process.h>

HANDLE  hMutex; //ミューテックスのハンドル

int count_A = 0; //ミューテックスで排他制御された変数
int count_B = 0; //排他制御されていない変数

unsigned __stdcall IncrementCounter(LPVOID  pParam)
{
  while (1)
  {
    // hMutexオブジェクトがシグナル状態になるまで待つ(待ち時間は無限INFINITE)
    DWORD ret = WaitForSingleObject(hMutex, INFINITE);
    if (ret == WAIT_FAILED)
    {
      printf("WaitForMultipleObjectsでエラー(%d)", GetLastError());
      return 0;
    }

    // count_Aのカウントアップ
    printf("count_A:");

    for (int i = 0; i < 10; i++)
    {
      printf("%d:", count_A);
      count_A++;
      Sleep(500);  // 0.5秒スリープ
    }

    printf("\n");

    // ミューテックスの解放
    ReleaseMutex(hMutex);

    // count_Bのカウントアップ
    printf("count_B:");

    for (int i = 0; i < 10; i++)
    {
      printf("%d:", count_B);
      count_B++;
      Sleep(500);  // 0.5秒スリープ
    }

    printf("\n");
  }
}

unsigned __stdcall resetCounter(LPVOID  pParam)
{
  while (1)
  {
    // hMutexオブジェクトがシグナル状態になるまで待つ(待ち時間は無限INFINITE)
    DWORD ret = WaitForSingleObject(hMutex, INFINITE);
    if (ret == WAIT_FAILED)
    {
      printf("WaitForMultipleObjectsでエラー(%d)", GetLastError());
      return 0;
    }

    count_A = 0;

    // ミューテックスの解放
    ReleaseMutex(hMutex);

    count_B = 0;
  }
}

// メイン関数
int _tmain(int argc, _TCHAR* argv[])
{
  HANDLE  hThread[2] = { 0 };  //ハンドル

  //ミューテックス生成
  hMutex = CreateMutex(NULL, FALSE, NULL);
  if (::GetLastError() == ERROR_ALREADY_EXISTS)
  {
    printf("ミューテックスの作成に失敗しました。(%d)", GetLastError());
    return 0;
  }

  int stackSize = 0;
  // スレッド1の作成
  hThread[0] = (HANDLE)_beginthreadex(NULL, stackSize, IncrementCounter, NULL, 0, NULL);
  if (hThread[0] == 0)
  {
    printf("スレッドの作成に失敗しました。(%d)", GetLastError());
    return 0;
  }

  // スレッド2の作成
  hThread[1] = (HANDLE)_beginthreadex(NULL, stackSize, resetCounter, NULL, 0, NULL);
  if (hThread[1] == 0)
  {
    printf("スレッドの作成に失敗しました。(%d)", GetLastError());
    return 0;
  }

  //スレッド1、2終了待ち
  DWORD ret = WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
  if (ret == WAIT_FAILED)
  {
    printf("WaitForMultipleObjectsでエラー(%d)", GetLastError());
    return 0;
  }

  //ハンドルクローズ
  CloseHandle(hThread[0]);
  CloseHandle(hThread[1]);
  CloseHandle(hMutex);

  return 0;
}