C++Builder2009のスレッドの基本的な使い方のまとめ。
スレッドクラスを作成する
- C++Builderのメニューから「ファイル」→「新規作成」→「その他」を選択します。
- 項目カテゴリから「C++Builderファイル」を選択します。
- 「スレッドオブジェクト」を選択し「OK」ボタンを押します。
- クラス名を入力し「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に限定されません。