2

私は最近、INDY10 TCP / IPサーバーの基本機能に関する1つのデモオープンソースプロジェクトを試してみましたが、INDYの内部マルチタスク実装とVCLコンポーネントとの相互作用の問題に遭遇しましたSOにはこのテーマに関するさまざまなトピックがあるため、単純なクライアントサーバーアプリケーションを作成し、提案されたソリューションとアプローチのいくつか、少なくとも正しく理解したものをテストすることにしました。以下に、SOで以前に提案されたアプローチを要約して確認し、可能であれば、このテーマに関する専門家の意見に耳を傾けたいと思います。

問題:indy10ベースのクライアント/サーバーアプリケーション内でスレッドセーフに使用できるようにVCLをカプセル化します。

開発環境の説明:Delphiバージョン:Delphi®XE2バージョン16.0INDYバージョン10.5.8.0OS Windows 7(32ビット)

記事で述べたように([VCLスレッドセーフですか?])(リンクを投稿するのに十分な評判がありません)マルチスレッド(マルチタスク)内で任意の種類のVCLコンポーネントを使用する場合は、特別な注意が必要です。応用。VCLはスレッドセーフではありませんが、スレッドセーフな方法で使用できます。方法と理由は通常、手元のアプリケーションによって異なりますが、少し一般化して、この問題に対するある種の一般的なアプローチを提案することができます。まず、INDY10の場合のように、VCLをデッドロックやデータの相互依存関係にさらすために、コードを明示的に並列化する必要はありません。つまり、複数のスレッドを作成して実行する必要はありません。

すべてのsclient-serverアプリケーションで、サーバーは複数の要求を同時に処理できる必要があるため、当然、INDY10はこの機能を内部的に実装します。これは、INDY10のクラスのセットが、プログラムのスレッドの作成、実行、および破棄の手順を内部で管理する責任があることを意味します。

コードがINDY10の内部動作にさらされ、スレッドの競合が発生する可能性がある最も明白な場所は、IdTCPServerExecute(TIdTCPServer onExecuteイベント)メソッドです。

当然、INDY10はスレッドセーフなプログラムフローを保証するクラス(ラッパー)を提供しますが、それらのアプリケーションと使用法について十分な説明を得ることができなかったため、カスタムメイドのアプローチを好みます。

以下に、この問題の処理を試みる(そしておそらく成功する)メソッド(提案された手法は、SOで見つけた以前のコメントに基づいています。Indy10のTIdThreadSafeクラスの使用方法)を要約します。

以下で私が取り組む質問は、特定のクラス「MyClass」をスレッドセーフにする方法です。

主なアイデアは、「MyClass」をカプセル化し、先入れ先出しの原則でそれにアクセスしようとするスレッドをキューに入れる一種のラッパークラスを作成することです。同期に使用される基になるオブジェクトは、[Windowsのクリティカルセクションオブジェクト]です。

クライアント/サーバーアプリケーションのコンテキストでは、「MyClass」にはサーバーのすべてのスレッドセーフでない機能が含まれるため、これらのプロシージャと関数が複数の作業スレッドによって同時に実行されないようにします。これは当然、コードの並列処理が失われることを意味しますが、アプローチは単純であり、そう思われるため、場合によっては、これは有用なアプローチである可能性があります。

ラッパークラスの実装:

constructor TThreadSafeObject<T>.Create(originalObject: T);
begin
  tsObject := originalObject; // pass it already instantiated instance of MyClass
  tsCriticalSection:= TCriticalSection.Create; // Critical section Object
end;

destructor TThreadSafeObject<T>.Destroy();
begin
  FreeAndNil(tsObject);
  FreeAndNil(tsCriticalSection);
  inherited Destroy;

end;


function TThreadSafeObject<T>.Lock(): T;
begin
  tsCriticalSection.Enter;
  result:=tsObject;

end;

procedure TThreadSafeObject<T>.Unlock();
begin
  tsCriticalSection.Leave;
end;

procedure TThreadSafeObject<T>.FreeOwnership();
begin
  FreeAndNil(tsObject);
  FreeAndNil(tsCriticalSection);
end;

MyClassの定義:

MyClass = class

  public
    procedure drawRandomBitmap(abitmap: TBitmap); //Draw Random Lines on TCanvas
    function decToBin(i: LongInt): String; //convert decimal number to Bin. 
    procedure addLineToMemo(aLine: String; MemoFld: TMemo); // output message to TMemo
    function randomColor(): TColor; 
  end;

使用法:

スレッドは順番に実行され、クリティカルセクションの現在の所有権を持つスレッド(tsCriticalSection.Enter;およびtsCriticalSection.Leave;)が終了するのを待つため、その所有権リレーを管理する場合は、1つの一意のインスタンスが必要です。TThreadSafeObject(シングルトンパターンの使用を検討できます)。したがって、以下を含めます。

tsMyclass:= TThreadSafeObject<MyClass>.Create(MyClass.Create);

Form.Createと

tsMyclass.Destroy;

Form.Closeで; ここで、tsMyclassはMyClass型のグローバル変数です。

使用法:

MyClassの使用法については、次のことを試してください。

with tsMyclass.Lock do
try
  addLineToMemo('MemoLine1', Memo1);
  addLineToMemo('MemoLine2', Memo1);
  addLineToMemo('MemoLine3', Memo1);
finally
  // release ownership
  tsMyclass.unlock;
end;

、ここで、Memo1はフォーム上のTMemoコンポーネントのインスタンスです。

これにより、tsMyClassがロックされているときに発生するすべてのことが、一度に1つのスレッドによってのみ実行されるようにする必要があります。ただし、このアプローチの明らかな欠点は、tsMyclassのインスタンスが1つしかないため、1つのスレッドがたとえばCanvasで描画しようとしていて、別のスレッドがメモに書き込んでいる場合でも、最初のスレッドは待機する必要があることです。 2番目に終了し、その場合にのみ、その仕事を実行できるようになります。

ここでの私の質問は次のとおりです。

  1. 上記の提案された方法は正しいですか?私はまだ競合状態に陥っていませんか、それともデータの競合が発生する可能性のあるコードに「抜け穴」がありますか?
  2. 一般に、アプリケーションのスレッドの安全性をテストするにはどうすればよいですか?

上記のアプローチは決して私自身のやり方ではないことを強調したいと思います。これは基本的に2で見つかった解決策の要約です。それにもかかわらず、私は、トピックに関するある種の閉鎖または提案された解決策のある種の有効性の証明を得るために、再度投稿することにしました。その上、彼らが言うように、繰り返しはすべての知識の母です。

4

3 に答える 3

4

これにより、tsMyClass がロックされているときに発生することはすべて、一度に 1 つのスレッドのみによって実行されるようにする必要があります。ただし、このアプローチの明らかな欠点は、tsMyclass のインスタンスが 1 つしかないため、1 つのスレッドがキャンバスなどに描画しようとしていて、別のスレッドがメモに書き込んでいる場合でも、最初のスレッドが待機する必要があることです。 2 番目に完了すると、そのジョブを実行できるようになります。

ここで大きな問題が 1 つあります。VCL (フォーム、描画など) がメイン スレッド上にあることです。同時スレッド アクセスをブロックした場合でも、更新はメイン スレッドのコンテキストで行う必要があります。これは Synhronize() を使用する必要がある部分です。ロック (Criticalsection) との大きな違いは、同期されたコードがメイン スレッドのコンテキストで実行されることです。最終結果は基本的に同じで、スレッド化されたコードがシリアル化され、そもそもスレッドを使用する利点が失われます。

于 2013-01-29T18:03:09.017 に答える
2

オブジェクト全体のロックは粗すぎる場合があります。

一部のプロパティまたはメソッドが他のプロパティまたはメソッドから独立している場合を想像してください。ロックが「グローバル」レベルで機能する場合、多くの操作が不必要にブロックされます。

「ロックの粒度を減らす」から– 同時実行の最適化

では、どうすればロックの粒度を減らすことができるでしょうか? 簡単に言えば、ロックの要求をできるだけ少なくすることです。基本的な考え方は、クラス スコープにロックを 1 つだけ持つのではなく、個別のロックを使用して、クラスの複数の独立した状態変数を保護することです。

于 2013-01-29T17:11:42.293 に答える
1

まず最初に、オブジェクトごとに LOCK を実装する必要はありません。Delphi は次のTMonitorクラスを使用してそれを行います。

TMonitor.Enter(WhateverObject);
try
  // Your code goes here.
finally TMonitor.Leave(WhateverObject);
end;

アプリケーションがシャットダウンしたときに必ず解放してWhateverObjectください。そうしないと、私が QC で公開したバグに遭遇します: http://qc.embarcadero.com/wc/qcmain.aspx?d=111795

第 2 に、アプリケーションをマルチスレッド化することは、もう少し複雑です。Enter/呼び出しの間に各呼び出しをラップすることはできませんLeave。「ロック」では、オブジェクトの機能とアクセスパターンを考慮する必要があります。Enter/Leave 内で呼び出しをラップすると、常に 1 つのスレッドだけがそのメソッドを実行するようになりますが、競合状態ははるかに複雑で、ロックされたメソッドへの連続した呼び出しから発生する可能性があります。各メソッドがロックされていて、一度に 1 つのスレッドだけがそれらのメソッドを呼び出したとしても、ロックされたオブジェクトの状態は、他のスレッドのアクティビティの結果として変化する可能性があります。

この種のコードは、シングル スレッド アプリケーションでは問題ありませんが、マルチ スレッドに切り替えると、メソッド レベルでロックするだけでは十分ではありません。

if List.IndexOf(Something) = -1 then
  List.Add(Something);
于 2013-01-29T17:16:03.673 に答える