キューを共有データバッファとして使用するプロデューサ - コンシューマパターンの多くの説明と例を参照してください。 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;
プロデューサ - コンシューマパターンは、データメッセージの非同期消費に関するものです。生産者アクティビティの低下または消費者活動の増加がこのパターンの最良のケースである。この場合、キューにデータがない場合、消費者スレッドはプロデューサがデータ要素を生成するまで待機するからである。パターンの最悪のケースは、プロデューサーがスパイクを持っているときです。スレッドプールを使用することはできますが、プール内のスレッド数には制限があります。 –
プロデューサ/コンシューマパターンではプロデューサアクティビティやコンシューマアクティビティの低下が急激に悪化する最悪のシナリオを処理するためにアプリケーションの設計が常に行われるため、キューが最適です。 –
最悪の場合のシナリオを処理するためにアプリケーションの設計が常に行われると、最悪の場合が有界キューを満たすため、有界キューは決して使用されません。一方、動的に割り当てられたキューは、メモリが使い果たされたときに最悪の場合に常に失敗します。最終的には、ファイルの使用がキューの使用よりも柔軟性があることを発見するかもしれません。 –