Top / Programming / C++Builder / C++Builderでマルチスレッドプログラミング / スレッドの排他制御

スレッドの排他制御

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;
}