C++Builder2009のスレッドの基本的な使い方のまとめ

C++Builder2009のスレッドの基本的な使い方のまとめ。

スレッドクラスを作成する

  1. C++Builderのメニューから「ファイル」→「新規作成」→「その他」を選択します。
  2. 項目カテゴリから「C++Builderファイル」を選択します。
  3. 「スレッドオブジェクト」を選択し「OK」ボタンを押します。
  4. クラス名を入力し「OK」ボタンを押します。

スレッドクラスのひな形が作成されます。

スレッドを実行する

Execute()に実行する処理を記述します。

void __fastcall TMyThread::Execute()
{
  for (int i = 0; i < 1000; i++) 
  {
    std::cout << "Nice!";
  }
}

スレッドクラスのインスタンスを作成し、起動します。

int _tmain(int argc, _TCHAR* argv[])
{
  //"Nice!"を出力するTMyThreadを生成して起動する
  TMyThread* thread = new TMyThread(false);
  //メインスレッドでは"Good!"を出力する
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  Sleep(5000);
  return 0;
}

TThreadクラスのコンストラクタの引数CreateSuspendedにfalseを指定すると、
インスタンス作成後、Execute()メソッドがすぐに呼び出されます。

スレッドを自動的に実行するのではなく明示的に実行する場合は、
コンストラクタの引数CreateSuspendedにtrueを指定し、
TThread.Resumeメソッドで実行します。

int _tmain(int argc, _TCHAR* argv[])
{
  #インスタンスの作成
  TMyThread* thread = new TMyThread(true);
  #スレッドの実行
  thread->Resume();
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  Sleep(5000);
  return 0;
}

メインスレッドが終了すると、すべてのスレッドが終了します。

スレッドの一時停止

TThread.Suspendメソッドを使うと、実行中のスレッドを一時停止することができます。

一時停止したスレッドを再実行するには、TThread.Resumeメソッドで呼び出します。

int _tmain(int argc, _TCHAR* argv[])
{
  //スレッドの実行
  TMyThread* thread = new TMyThread(false);
  //スレッドの一時停止
  thread->Suspend();
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  //スレッドの再実行
  thread->Resume();
  Sleep(5000);
  return 0;
}

スレッドの排他制御

Synchronizeメソッド

Synchronizeメソッドは、制御をメインスレッド側に移して関数を実行します。
関数の処理が終わるまでワーカースレッドは待たされます。並行動作ではありません。
異なるスレッドが所有するVCLオブジェクトのメソッドやプロパティにアクセスするときは、
Synchronizeメソッドを使用しなければなりません。

void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Synchronize(&WriteMsg);

  FMsg = "End";
  Synchronize(&WriteMsg);
}
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Synchronizeは並行動作ではないため、実行が終わるまでワーカースレッドは待たされます。

実行結果は次のようになります。

Start
End

Queueメソッド

Queueメソッドは、Synchronizeメソッドと同様、制御をメインスレッド側に移して関数を実行します。
ただし、メインスレッド側の実行は非同期で並行して行われるます。
ワーカースレッドは、関数の処理が終わるのを待たされることはありません。

//---------------------------------------------------------------------------
void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Queue(&WriteMsg);

  FMsg = "End";
  Queue(&WriteMsg);
}
//---------------------------------------------------------------------------
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Queueでは、メインスレッド側の実行は非同期で並行して行われます。

実行結果は次のようになります。

End
End

WriteMsg()の中でSleep(1000)している間に、Execute()でFMsgが”End”に更新されています。

クリティカルセクション(TCriticalSectionクラス)

クリティカルセクションは、複数のスレッドが同時に一つのリソースにアクセスしないように排他制御を行います。

銀行口座を例にした次のサンプルコードでは、
預金と引き出しが同時に行われないように
クリティカルセクションで排他制御を行います。

class TBank
{
public:
  TBank(int Money)
    : FMoney(Money), FCriticalSection(new TCriticalSection) {};
  ~TBank() { delete FCriticalSection; };
  //預金する
  void Deposit(int Money) 
  {
    FCriticalSection->Acquire(); //ほかのスレッドをロックアウトする
    try
    {
      FMoney += Money;
    }
    __finally
    {
      FCriticalSection->Release();
    }
  };
  //引き出す
  bool Withdraw(int Money) 
  {
    FCriticalSection->Acquire(); //ほかのスレッドをロックアウトする
    bool result = false;
    try
    {
      if (FMoney > Money) {
        FMoney -= Money;
        result = true; //引き出せた
      } else {
        result = false; //残高不足
      }
    }
    __finally
    {
      FCriticalSection->Release();
    }
    return result;
  };
private:
  int FMoney;
  TCriticalSection* FCriticalSection;
};

セマフォ(TSemaphoreクラス)

クリティカルセクション(TCriticalSectionクラス)は、ある領域を「たった1つのスレッド」だけが実行できるように制限します。
これに対し、セマフォは、ある領域を「最大でN個のスレッド」まで実行できるように制限します。

次の実行例では、10個のスレッドが入れ替わりながらリソースを使用します。
同時に使用されているリソースは最大3個に限定されます。

std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);

//数の制限があるリソース
class TBoundedResource
{
public:
  //コンストラクタ(Permitsはリソースの個数)
  TBoundedResource(int Permits)
    : FSemaphore(new TSemaphore(NULL, Permits, Permits, "", false)), FUsed(0) {};
  //リソースを使用する
  void Use()
  {
    FSemaphore->Acquire();
    try
    {
      DoUse();
    }
    __finally
    {
      FSemaphore->Release();
    }
  };
private:
  TSemaphore* FSemaphore;
  int FUsed;
  //リソースを実際に使用する
  void DoUse()
  {
    PrintBegin();
    Sleep(random(1000));
    PrintEnd();
  }
  void PrintBegin()
  {
    CriticalSection->Acquire();
    std::cout << "BEGIN: used = " << ++FUsed << std::endl;
    CriticalSection->Release();
  }
  void PrintEnd()
  {
    CriticalSection->Acquire();
    std::cout << "END: used = " << FUsed-- << std::endl;
    CriticalSection->Release();
  }
};

//リソースを使用するスレッド
class TUserThread : public TThread
{
private:
  TBoundedResource* FResource;
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      FResource->Use();
      Sleep(random(1000));
    }
  }
public:
  __fastcall TUserThread(TBoundedResource* Resource)
    : TThread(false), FResource(Resource) {};
};

int _tmain(int argc, _TCHAR* argv[])
{
  //3個のリソースを用意する
  TBoundedResource* resource = new TBoundedResource(3);

  //10個のスレッドが利用する
  for (int i = 0; i < 10; i++)
  {
    new TUserThread(resource.get());
  }

  Sleep(10000);
  return 0;
}

スレッドの実行が終了するまで待つ

TThread.WaitFor()は、処理が終了するまで戻りません。

TMyThread* thread = new TMyThread(false);
//スレッドが終了するまで待つ
thread->WaitFor();
//スレッドが終了した
std::cout << "END";

実行中のスレッドを終了する

TThread.Terminate メソッドはスレッドのTerminatedプロパティをtrueにします。
スレッドはTerminatedプロパティがtrueになったら終了するように実装する必要があります。

void __fastcall TMyThread::Execute()
{
  //TThread.Terminateメソッドが呼ばれるまで出力を繰り返す
  while (!Terminated)
  {
    std::cout << "Nice!";
    Sleep(100);
  };
}

int _tmain(int argc, _TCHAR* argv[])
{
  TMyThread* thread = new TMyThread(false);
  //5秒待機
  Sleep(5000);
  //スレッドを終了する
  thread->Terminate();
  return 0;
}

イベントの発生を通知する

TEventを使用するとスレッドからイベントを通知することができます。

class TMyThread : public TThread
{
private:
  TEvent* FEvent;
protected:
  void __fastcall Execute()
  {
    for (int i = 0; i < 1000; ++i)
    {
      std::cout << "Nice!";
    };
    FEvent->SetEvent();
  }
public:
  __fastcall TMyThread(bool CreateSuspended, TEvent* Event)
    : TThread(CreateSuspended), FEvent(Event) {};
};

int _tmain(int argc, _TCHAR* argv[])
{
  TEvent* event = new TEvent(false);
  TMyThread* thread = new TMyThread(false, event);
  //10秒間スレッドからのイベントの通知を待つ
  if (event->WaitFor(10000) == wrSignaled)
  {
    //スレッドからの通知があった
    std::cout << "END";
  }
  else
  {
    //スレッドからの通知がない、または時間切れ
    std::cout << "ERROR";
  }
  return 0;
}

複数のスレッドが同じTEventのインスタンスを使いTEvent.WaitForで待機しているとき、
TEvent.SetEvent()によって実行されるスレッドは1つだけです。
Javaのwait()とnotify()に対応します。notifyAll()ではありません。

マルチスレッドの学習には『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』がわかりやすくておすすめ。
解説にJavaを使用していますが、マルチスレッドの知識はJavaに限定されません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください