2017-01-04 7 views
0

キューを共有データバッファとして使用するプロデューサ - コンシューマパターンの多くの説明と例を参照してください。 1つの要素データバッファではなくキューを使用する理由は何ですか?なぜプロデューサとコンシューマ間の通信バッファとしてキューを使用するのですか?

すべてのプロデューサ - コンシューマの実装では、プロデューサはコンシューマがデータを処理できるよりも速くデータを生成するか、プロデューサはコンシューマがデータを処理できるよりも遅いデータを生成します。つまり、キューは最終的に最大容量まで充填されるか、消費者がそのデータ要素を消費する前に1つのデータ要素のみを含むことになります。キューがいっぱいになると、プロデューサは別のデータ要素を追加する前にコンシューマを待つ必要があります。キューに要素が1つだけ含まれている場合、消費者は別の要素を消費する前にプロデューサを待つ必要があります。つまり、キューは単一の要素バッファのように動作します。

キューの操作には、単一のデータ要素を処理する場合よりもはるかに多くの処理オーバーヘッドとプログラムの複雑性が必要です。追加された複雑さとオーバーヘッドを正当化するには、ある種の利点があるはずです。

プロデューサ - コンシューマパターンには、2つの大きなサブパターンがあります。 1つは、同期されたプロデューサ - コンシューマパターンです。このパターンは、コンシューマがコンシューマによって生成された各データ要素を消費し、コンシューマがそれを1回だけ消費することを保証します。同期化されたパターンについては上で説明した。 2番目はサンプリング消費者です。

サンプリングコンシューマは、プロデューサによって生成されたデータ要素のサンプルのみを消費します。消費者がデータ要素を消費するよりも速くデータ要素を消費する場合、消費者はバッファをオーバーサンプリングすると言われる。消費者がデータ要素を生産するよりも遅いデータ要素を消費する場合、消費者はデータ要素を過小評価すると言われる。アンダー・サンプリング・デザインの例は、1Hz(1秒間に1回)の割合でレポートを作成しながら、100Hz(1秒間に100回)のレートで温度の読み取り値を生成する気象ステーションです。消費者は毎秒100データ点を読み取る必要はなく、その平均値を生成する必要があります。代わりに、毎秒1回の読み取りが必要で、その読みを報告することができます。消費者が読み取り値の1%しかサンプリングしない場合、バッファ内にデータ要素のキューを設ける必要はない。

制限されたキューは、メモリ消費を制御するためによく使用されます。バインドされていないキューには常にヒープメモリが不足する可能性があり、アプリケーションのエラーが発生する可能性があります。

多くのプロデューサが存在する場合、有界マルチエレメントキューは良いアイデアのように思えるかもしれません。多くのプロデューサによって生成されたすべてのデータをまったく同じ方法で処理する必要がある場合、そのデータをすべて共通のバッファに書き込んで、1つ以上のコンシューマがデータを処理できるようにするのが妥当かもしれません。マルチエレメントキューには、いくつかのコストが伴います。マルチエレメントキューは、各データエレメントのサイズにバウンドキュー内のエレメントの数を掛けたものにほぼ等しいレートでメモリを使用する。マルチエレメントキューは、単一エレメントキューより複雑なロジックを必要とします。複雑さは常に正確さの敵です。

マルチエレメントキューが使用されている場合、コンシューマはキュー内にあったデータエレメントを常に最長時間読み込みます。結局、キューはFIFOデータ構造です。プロデューサは、キューがいっぱいになるまでキューに新しい要素を書き込みます。その時点で、プロデューサはキュー上の領域を待つか、キュー上の最新の要素を上書きします。消費者は、プロデューサが何をしているかに関わらず、キュー上の最も古い要素を読み込みます。

キューがキューの動作を満たしている場合は、1つの要素キューの動作とまったく同じです。ブロッキングプロデューサ/コンシューマパターンでは、フルキューは、プロデューサに、消費者がキューから読み出すのを待つように強制します。 1つの要素キューは、常にフルまたは空です。完全な複数要素キューは、消費されるのを待っている多くのデータ要素と、消費されることができる単一の要素とを含むだけである。

単一の要素キューは、対象となることを待機しているデータのキュー内の時間を単純に排除します。

パターンのコンシューマ側では、コンシューマはキュー内の要素の数に関係なく、キュー上の最も古い要素のみを読み取ることができます。要素が消費者に利用可能であるか、利用できない要素。キューのサイズは、消費者には見えません。キューが空の場合、コンシューマは使用可能なデータの待機を中断するか、使用可能なデータのキューをアクティブにサンプリングする必要があります。前述のように、サンプリングキューの場合、消費者は単にキュー上で最も古い値を処理することができ、キューは自分自身を空ではない。

サンプリングコンシューマをマルチエレメントキューで実装することは非常に困難です。 1つの要素キューが行います。プロデューサはキューにあるものを単に上書きし、コンシューマはキューにあるものをすべて読み込みます。

以下は、Adaで書かれたサンプリングプロデューサ/コンシューマパターンの例です。

------------------------------------------------------------------ 
-- Sampling Consumer -- 
------------------------------------------------------------------ 
with Ada.Text_IO; use Ada.Text_IO; 

procedure Sampling_PC is 
    protected Buf is 
     procedure Write(Item : in Integer); 
     function Read return Integer; 
     procedure Set_Done; 
     function Get_Done return Boolean; 
    private 
     Value : Integer := Integer'First; 
     Is_Done : Boolean := False; 
    end Buf; 

    protected body Buf is 
     procedure Write(Item : in Integer) is 
     begin 
     Value := Item; 
     end Write; 
     function Read return Integer is 
     begin 
     return Value; 
     end Read; 
     procedure Set_Done is 
     begin 
     Is_Done := True; 
     end Set_Done; 
     function Get_Done return Boolean is 
     begin 
     return Is_Done; 
     end Get_Done; 
    end Buf; 

    task Consumer; 

    task body Consumer is 
    begin 
     while not Buf.Get_Done loop 
     Put_Line("Consumer read" & Integer'Image(Buf.Read)); 
     end loop; 
    end Consumer; 

begin 
    for I in 1..10 loop 
     Put_Line("Producer writing" & Integer'Image(I)); 
     Buf.Write(I); 
    end loop; 
    Buf.Set_Done; 
end Sampling_PC; 

Adaのタスクに不慣れな人には、いくつかの説明が必要な場合があります。上記の例では、Bufは保護されたオブジェクトです。 Adaの用語では、保護されたオブジェクトは、タスク間の共有バッファとして使用されるオブジェクトです(スレッドに類似しています)。各バッファは、内部データ要素にアクセスするためのメソッドを実装します。メソッドの種類は、プロシージャ、エントリ、および関数です。プロシージャーには、保護オブジェクトへの無条件の読み取り/書き込みアクセス権があります。各プロシージャーは、保護されたオブジェクトの読み取り/書き込みロックを自動的に操作します。エントリは、スレッド内の条件変数と同じように、制御条件を追加する点を除けば、プロシージャに非常に似ています。エントリーは、複数のライターと重複する読み取り/書き込み操作を防ぐために読み取り/書き込みロックを実装するだけでなく、条件がTRUEになるのを待つタスクのキューも実装します。保護オブジェクトの関数は、保護オブジェクトへの読み取り専用アクセスを提供します。関数は、複数のタスクが同時に保護されたオブジェクトから読み取ることができるように、共有読み取りロックを自動的に操作します。同時読み取りでは、保護されたオブジェクトが破損することはありません。

上記の例では、プロシージャと関数のみを使用しています。コンシューマー・タスクは、関数を使用して保護オブジェクトを読み取りますが、プロジェクト(この場合プログラム主タスク)は、プロシージャーを使用して保護オブジェクトに書き込みます。

複数のプロデューサおよび複数のコンシューマを使用して生産者 - 消費者パターンの例は、プロデューサーのいずれかが速く、消費者が、データを処理することができるよりも、データを生成するすべての生産者 - 消費者実装で

------------------------------------------------------------------ 
-- Multiple producers and consumers sharing the same buffer -- 
------------------------------------------------------------------ 
with Ada.Text_IO; use Ada.Text_Io; 

procedure N_Prod_Con is 
    protected Buffer is 
     Entry Write(Item : in Integer); 
     Entry Read(Item : Out Integer); 
    private 
     Value : Integer := Integer'Last; 
     Is_New : Boolean := False; 
    end Buffer; 
    protected body Buffer is 
     Entry Write(Item : in Integer) when not Is_New is 
     begin 
     Value := Item; 
     Is_New := True; 
     end Write; 
     Entry Read(Item : out Integer) when Is_New is 
     begin 
     Item := Value; 
     Is_New := False; 
     end Read; 
    end Buffer; 

    task type Producers(Id : Positive) is 
     Entry Stop; 
    end Producers; 
    task body Producers is 
     Num : Positive := 1; 
    begin 
     loop 
     select 
      accept Stop; 
      exit; 
     or 
      delay 0.0001; 
     end select; 
     Put_Line("Producer" & Integer'Image(Id) & " writing" & Integer'Image(Num)); 
     Buffer.Write(Num); 
     Num := Num + 1; 
     end loop; 
    end Producers; 

    task type Consumers(Id : Positive) is 
     Entry Stop; 
    end Consumers; 

    task body Consumers is 
     Num : Integer; 
    begin 
     loop 
     select 
      accept stop; 
      exit; 
     or 
      delay 0.0001; 
     end select; 
     Buffer.Read(Num); 
     Put_Line("Consumer" & Integer'Image(ID) & " read" & Integer'Image(Num)); 
     end loop; 
    end Consumers; 
    P1 : Producers(1); 
    P2 : Producers(2); 
    P3 : Producers(3); 
    C1 : Consumers(1); 
    C2 : Consumers(2); 
    C3 : Consumers(3); 
begin 
    delay 0.2; 
    P1.Stop; 
    P2.Stop; 
    P3.Stop; 
    C1.Stop; 
    C2.Stop; 
    C3.Stop; 
end N_Prod_Con; 
+0

プロデューサ - コンシューマパターンは、データメッセージの非同期消費に関するものです。生産者アクティビティの低下または消費者活動の増加がこのパターンの最良のケースである。この場合、キューにデータがない場合、消費者スレッドはプロデューサがデータ要素を生成するまで待機するからである。パターンの最悪のケースは、プロデューサーがスパイクを持っているときです。スレッドプールを使用することはできますが、プール内のスレッド数には制限があります。 –

+0

プロデューサ/コンシューマパターンではプロデューサアクティビティやコンシューマアクティビティの低下が急激に悪化する最悪のシナリオを処理するためにアプリケーションの設計が常に行われるため、キューが最適です。 –

+0

最悪の場合のシナリオを処理するためにアプリケーションの設計が常に行われると、最悪の場合が有界キューを満たすため、有界キューは決して使用されません。一方、動的に割り当てられたキューは、メモリが使い果たされたときに最悪の場合に常に失敗します。最終的には、ファイルの使用がキューの使用よりも柔軟性があることを発見するかもしれません。 –

答えて

2

プロデューサは消費者がデータを処理できるよりも遅いデータを生成します。つまり、キューは最終的に最大容量まで充填されるか、消費者がそのデータ要素を消費する前に1つのデータ要素のみを含むことになります。

これは、生産者が一定の割合で生産し、消費者が一定の割合で消費することを前提としています。また、コンシューマの数が固定されていることも前提としています。これは、ロードに応答してスレッド数を変更できるスレッドプールの実装などの場合とは必ずしも一致しません。

これらの前提条件をすべて緩和すれば、キューを持つことが理にかなっています。とりわけ、システムは一時的な需要の急増に優雅に対処できます。

これは、容量1のキュー(ブロッキングまたは上書き)がその用途を持たないとは限りません。状況によっては正確に必要なものがあります。

+0

ロードバランシングの概念さえ、いくつかの制限があります。たとえば、Webサーバーが注文処理を処理している場合、エンドユーザーとデータベース内の特定のショッピングカートとの間に専用の接続を確立する必要があります。エンドユーザーは通常のレートでデータを入力しませんが、ショッピングカートのイベントを待つ専用のコンシューマーがデータベースのエンドにあります。このようなシステムではスレッドプールを使用することができますが、顧客とデータベースの間にキューを使用する利点はありません。 –

+0

キューは、プロデューサアクティビティが短期間に急上昇したり、短期間に消費者アクティビティが低下した場合にのみ役立ちます。プロデューサのアクティビティが低下したり、消費者の活動が急上昇したりした場合、キューは役立ちません。実際、データ生産における一時的なスパイクを優雅に処理するのと同じ種類の能力は、単一の要素キューと消費者スレッドのプールを使用して処理することができます。増加した活動は、より多くの消費者のデータを処理することで対応できます。 –

関連する問題