Producer-Consumerパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある
「Producer-Consumerパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

C++Builder2009にはJavaにおけるsynchronizedやnotifyAll()がないので試行錯誤。

C++Builder2009にはJavaにおけるnotiryAll()がありません。
TTable.Put()とTTable.Take()を同時に実行するのは問題ありませんので、
TTable.Put()とTTable.Take()にそれぞれクリティカルセッションを用い、
それぞれのメソッドが同時に実行されないようにしました。

  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    TLock lock(FPutCriticalSection.get());
    …

  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    TLock lock(FTakeCriticalSection.get());
    …

JavaにおけるnotifyAll()がないため、TEventによるイベント通知を使用しました。
「ケーキを置くイベント」と「ケーキをとるイベント」を用意しました。

  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    …
    //テーブルにはケーキがこれ以上おけないとき
    while (FBuffer.size() >= FCount)
    {
      FPutEvent->WaitFor(INFINITE); //ケーキをとるイベントを待つ
    }

  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    …
    //ケーキをとる
    std::string cake = FBuffer.front();
    FBuffer.pop();
    FPutEvent->SetEvent(); //ケーキをとるイベントの通知

全体のコードです。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//Javaのsynchronizedを実現するためのRAII
class TLock
{
public:
  TLock(TCriticalSection* CriticalSection): FCriticalSection(CriticalSection)
  {
    FCriticalSection->Acquire();
  };
  ~TLock()
  {
    FCriticalSection->Release();
  }
private:
  TCriticalSection* FCriticalSection;
};
//テーブルを表すクラス
class TTable
{
public:
  TTable(const int Count)
    : FCount(Count),
      FPutCriticalSection(new TCriticalSection),
      FTakeCriticalSection(new TCriticalSection),
      FPutEvent(new TEvent(NULL, false, false, "", false)),
      FTakeEvent(new TEvent(NULL, false, false, "", false))
  {
  };
  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    FPutCriticalSection->Acquire();
    //テーブルにはケーキがこれ以上おけないとき
    while (FBuffer.size() >= FCount)
    {
      FPutEvent->WaitFor(INFINITE); //ケーキをとるイベントを待つ
    }
    Print((boost::format("%s puts %s") % ThreadName % Cake).str());
    FBuffer.push(Cake);
    FTakeEvent->SetEvent();
    FPutCriticalSection->Release();
  }
  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    FPutEvent->Acquire();
    //テーブルにはケーキがないとき
    while (FBuffer.empty())
    {
      FTakeEvent->WaitFor(INFINITE); //ケーキを置くイベントを待つ
    }
    std::string cake = FBuffer.front();
    FBuffer.pop();
    FPutEvent->SetEvent();
    Print((boost::format("%s takes %s") % ThreadName % cake).str());
    FPutEvent->Release();
    return cake;
  }
private:
  std::queue<std::string> FBuffer;
  const unsigned int FCount; //テーブルにおけるケーキの数
  std::unique_ptr<TCriticalSection> FPutCriticalSection;
  std::unique_ptr<TCriticalSection> FTakeCriticalSection;
  std::unique_ptr<TEvent> FPutEvent; //ケーキが置いたときのイベント
  std::unique_ptr<TEvent> FTakeEvent; //ケーキをとったときのイベント
};
//ケーキを作ってテーブルに置く
class TMakerThread : public TThread
{
public:
  __fastcall TMakerThread(std::string Name, TTable* Table)
    : TThread(false), FName(Name), FTable(Table) {};
protected:
  void __fastcall Execute()
  {
    boost::format fmt("[ Cake No. %d by %s ]");
    while (true)
    {
      Sleep(random(1000));
      std::string cake = (fmt % NextId() % FName).str();
      FTable->Put(FName, cake);
    }
  }
private:
  const std::string FName;
  TTable* FTable;
  static int NextId()
  {
    static int id = 0;
    return id++;
  }
};
//テーブルに置かれたケーキを食べるクラス
class TEaterThread : public TThread
{
public:
  __fastcall TEaterThread(std::string Name, TTable* Table)
    : TThread(false), FName(Name), FTable(Table) {};
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      std::string cake = FTable->Take(FName);
      Sleep(random(1000));
    }
  }
private:
  const std::string FName;
  TTable* FTable;
};

int _tmain(int argc, _TCHAR* argv[])
{
  TTable* table = new TTable(3); //ケーキを3個までおけるテーブルを作る
  new TMakerThread("TMakerThread-1", table);
  new TMakerThread("TMakerThread-2", table);
  new TMakerThread("TMakerThread-3", table);
  new TEaterThread("TEaterThread-1", table);
  new TEaterThread("TEaterThread-2", table);
  new TEaterThread("TEaterThread-3", table);

  Sleep(INFINITE);
  return 0;
}

コメント

コメントを残す

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

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