TThreadの“ Synchronize”を使用する方が良いですかまたはメインスレッドと子スレッド間のIPCにウィンドウメッセージを使用しますか?

StackOverflow https://stackoverflow.com/questions/1806339

  •  05-07-2019
  •  | 
  •  

質問

Delphi 2007で作成された、かなり単純なマルチスレッドVCL guiアプリケーションがあります。メインフォームのグリッドコントロールを更新する必要がある複数の子スレッド(最大16同時)で処理を行います(単に文字列をグリッド)。子スレッドのいずれも互いに対話することはありません。

最初の設計では、 TThreadの「同期」を使用して、現在実行中のスレッド内のグリッドコントロールフォームを更新します。ただし、Synchronizeを呼び出すと、呼び出し時にメインスレッドであるかのように実行されることを理解しています。一度に最大16個のスレッドを実行すると(そして、子スレッドの処理の大部分は< 1秒から〜10秒かかります)、Window Messagesはより良い設計でしょうか?

この時点で、子スレッドが(複数の文字列のレコードで構成される)Windowsメッセージを投稿し、メインスレッドがリスナーを持ち、メッセージを受信したときにグリッドを更新するようになりました。

この状況でのIPCの最良の方法について意見はありますか?ウィンドウメッセージまたは「同期」?

ウィンドウメッセージを使用する場合、TCriticalSection(enter and leave)ブロックでグリッドに投稿するコードをラップすることをお勧めしますか?または、メインスレッドのグリッドに書き込みを行うので(ウィンドウメッセージハンドラーの関数内で)スレッドの安全性について心配する必要はありませんか?

役に立ちましたか?

解決

編集:

Delphi 4および5(私の作業のほとんどでまだ使用しているDelphiバージョン)以降、実装の詳細の多くが変更されたようです。また、アレンバウアーは次のようにコメントしています:

  

D6以降、TThreadはSendMessageを使用しなくなりました。スレッドセーフなワークキューを使用します。メインスレッド用に配置されています。メインスレッドにメッセージが投稿され、作業が利用可能であり、バックグラウンドスレッドがイベントでブロックされていることを示します。メインメッセージループがアイドル状態になる直前に、「CheckSynchronize」を呼び出します。作業が待機しているかどうかを確認します。その場合、それを処理します。作業項目が完了すると、バックグラウンドスレッドがブロックされるイベントが完了を示すように設定されます。 D2006タイムフレームで導入された、ブロックしないTThread.Queueメソッドが追加されました。

修正いただきありがとうございます。そのため、元の回答の詳細を一粒の塩で取ります。

ただし、これはコアポイントに実際には影響しません。私は今でも Synchronize()のアイデア全体に致命的な欠陥があると主張しており、これは現代のマシンのいくつかのコアを占有させようとする瞬間に明らかになります。 「同期」しないでくださいあなたのスレッドは、終了するまで動作させます。それらの間のすべての依存関係を最小限に抑えるようにしてください。特にGUIを更新するとき、これが完了するのを待つ理由は絶対にありません。 Synchronize() SendMessage()を使用するか、 PostMessage()を使用するかにかかわらず、結果のロードブロックは同じです。


Synchronize()は内部で SendMessage()を使用するため、ここで提示するものはまったく代替手段ではありません。ですから、足で自分を撃つためにどの武器を使いたいのかという問題です。

Delphi 2 VCLに TThread が導入されて以来、

Synchronize()は私たちと共にありました。 VCL。

どのように機能しますか?メインスレッドで作成されたウィンドウへの SendMessage()呼び出しを使用し、呼び出されるパラメーターレスオブジェクトメソッドのアドレスを渡すためにメッセージパラメーターを設定します。 Windowsメッセージは、宛先ウィンドウを作成し、そのメッセージループを実行するスレッドでのみ処理されるため、スレッドを中断し、メインVCLスレッドのコンテキストでメッセージを処理し、メソッドを呼び出し、メソッドの後にのみスレッドを再開します。実行が終了しました。

では、何が問題なのか(および SendMessage()を直接使用する場合も同様に何が問題なのか)?いくつかのこと:

  • 任意のスレッドに別のスレッドのコンテキストでコードを実行させると、2つのスレッドコンテキストスイッチが強制され、CPUサイクルが不必要に消費されます。
  • VCLスレッドがメッセージを処理して同期メソッドを呼び出す間、他のメッセージは処理できません。
  • 複数のスレッドがこのメソッドを使用する場合、それらはすべてブロックし、 Synchronize()または SendMessage()が戻るのを待ちます。これは巨大なボトルネックを作成します。
  • デッドロックが発生するのを待っています。スレッドが同期オブジェクトを保持しながら Synchronize()または SendMessage()を呼び出し、メッセージの処理中にVCLスレッドが同じ同期オブジェクトを取得する必要がある場合、アプリケーションはロックします
  • スレッドハンドルを待機しているAPI呼び出しについても同じことが言えます-メッセージを処理する何らかの手段なしで WaitForSingleObject()または WaitForMultipleObjects()を使用すると、デッドロックが発生しますスレッドがこれらの方法で「同期」する必要がある場合他のスレッドと。

では、代わりに何を使用しますか?いくつかのオプションについて説明します。

  • SendMessage()(または PostThreadMesの代わりに PostMessage()を使用します2つのスレッドが両方ともVCLスレッドでない場合、sage())。ただし、送信スレッドと受信スレッドはまったく同期されていないため、メッセージが到着した時点で無効になるメッセージパラメーターのデータを使用しないことが重要です。そのため、他の何らかの手段を使用して、 、オブジェクト参照またはメモリのチャンクは、送信スレッドがもう存在しない場合でも、メッセージが処理されるときに有効です。

  • スレッドセーフなデータ構造を作成し、ワーカースレッドからデータを入れて、メインスレッドからそれらを消費します。 PostMessage()は、処理する新しいデータが到着したことをVCLスレッドに警告するためだけに使用しますが、毎回メッセージを投稿しないでください。データの連続ストリームがある場合は、VCLスレッドでデータをポーリングすることもできます(タイマーを使用することもできます)が、これは貧弱な人向けのバージョンです。

  • 低レベルのツールは使用しないでください。少なくともDelphi 2007を使用している場合は、 OmniThreadLibrary をダウンロードして、スレッドではなくタスクの観点から考え始める。このライブラリには、スレッド間のデータ交換と同期のための多くの機能があります。また、スレッドプールの実装もあります。これは良いことです。使用するスレッドの量は、アプリケーションだけでなく、実行中のハードウェアにも依存するため、実行時にのみ多くの決定を下すことができます。 OTLでは、スレッドプールスレッドでタスクを実行できるため、システムは実行時に同時スレッドの数を調整できます。

編集:

再読み取りで、 SendMessage()を使用するつもりはないが、 PostMessage()を使用することに気付きました-上記のいくつかは適用されません、しかし、私はそれをそのままにしておきます。ただし、対処したい質問にはさらにいくつかのポイントがあります:

  

一度に最大16個のスレッドを実行すると(そして、子スレッドの処理の大部分は< 1秒から〜10秒かかります)、Window Messagesの方が良いでしょうか?

各スレッドから1秒に1回、またはそれ以上の期間にわたってメッセージを投稿する場合、デザインは問題ありません。 Windowsメッセージキューの長さは有限であり、カスタムメッセージは通常のメッセージ処理にあまり干渉しないため(プログラムが応答しなくなったように見えるため)、毎秒1スレッドあたり数百以上のメッセージを投稿することはできません。

  

子スレッドがWindowsメッセージ(複数の文字列のレコードで構成される)を投稿する場所

ウィンドウメッセージにレコードを含めることはできません。 2つのパラメーターを持ち、1つは WPARAM タイプ、もう1つは LPARAM タイプです。このようなレコードへのポインタはこれらのタイプのいずれかにしかキャストできないため、レコードの有効期間を何らかの方法で管理する必要があります。動的に割り当てる場合は、それも解放する必要があり、エラーが発生しやすくなります。スタック上のレコードまたはオブジェクトフィールドにポインターを渡す場合、メッセージが処理されるときにそれが有効であることを確認する必要があります。これは送信されたメッセージよりも投稿されたメッセージの方が困難です。

  

TCriticalSection(enter and leave)ブロックでグリッドに投稿するコードをラップすることを提案しますか?または、メインスレッドのグリッドに書き込みを行うので(ウィンドウメッセージハンドラーの関数内で)スレッドの安全性について心配する必要はありませんか?

PostMessage()呼び出しはすぐに戻るため、これを行う必要はありません。そのため、この時点で同期は必要ありません。スレッドの安全性を心配する必要がありますが、残念ながらいつを知ることはできません。同期オブジェクトを使用して、アクセスのためにデータを常にロックする ことにより、データへのアクセスがスレッドセーフであることを確認する必要があります。本当に達成する方法はありません

他のヒント

ところで、 TThread.Queue( ) の代わりに TThread。 Synchronize() Queue()は非同期バージョンで、呼び出しスレッドをブロックしません:

Queue はD8以降利用可能です。)

Synchronize()または Queue()の方が好きです。プレーンなメッセージ送信(制御なし)よりも理解しやすく(他のプログラマーにとって)、オブジェクト指向が優れているからです。それについて、またはデバッグできる!)

正しい方法と間違った方法があると確信していますが。両方のメソッドを使用してコードを記述しましたが、戻り続けるのはSendMessageメソッドであり、その理由はわかりません。

SendMessageとSynchronizeを使用しても、実際には違いはありません。どちらも基本的に同じ働きをします。 SendMessageを使用し続ける理由は、より多くの制御を認識しているからだと思いますが、わかりません。

SendMessageルーチンにより、呼び出し元スレッドは一時停止し、送信先ウィンドウが送信されたメッセージの処理を完了するまで待機します。このため、メインアプリスレッドは、呼び出しの間、呼び出し元の子スレッドに本質的に同期されます。 Windowsメッセージハンドラーでクリティカルセクションを使用する必要はありません。

データ転送は、呼び出しスレッドからメインアプリケーションスレッドへの基本的な1つの方法です。 message.resultで整数型の値を返すことができますが、メインスレッドのメモリオブジェクトを指すものは何も返しません。

2つのスレッドは「同期」されているため、そのポイントとメインアプリスレッドは現在SendMessageに対応しているため、他のスレッドが同時に入ってデータを破壊することを心配する必要もありません。そのため、クリティカルセクションやその他の種類のスレッドセーフ対策の使用について心配する必要はありません。

簡単なことのために、単一のメッセージ(wm_threadmsg1)を定義し、wparamフィールドとlparamフィールドを使用して、ステータスメッセージをやり取り(整数)できます。より複雑な例については、lparamを介して文字列を渡し、longintに型キャストして戻すことができます。 A-la longint(pchar(myvar))またはD2009以降を使用している場合はpwidecharを使用します。

すでにSynchronizeメソッドで動作している場合、変更を加えるためにそれをやり直すことを心配しません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top