イベントのポーリングは、オブザーバーパターンを使用するよりも優れていますか?


41

イベントのポーリングがオブザーバーパターンを使用するよりも優れているシナリオはありますか?私はポーリングを使用することを恐れており、誰かが良いシナリオをくれた場合にのみ使用を開始します。私が考えることができるのは、オブザーバーのパターンがポーリングよりも優れていることだけです。このシナリオを考慮してください:

あなたは自動車シミュレーターをプログラミングしています。車はオブジェクトです。車の電源が入ったらすぐに、「vroom vroom」サウンドクリップを再生します。

これは2つの方法でモデル化できます。

ポーリング:車オブジェクトを毎秒ポーリングして、オンになっているかどうかを確認します。オンになったら、サウンドクリップを再生します。

オブザーバーパターン:車をオブザーバーパターンのサブジェクトにします。それがオンになったときに、すべてのオブザーバーに「オン」イベントを公開します。車をリッスンする新しいサウンドオブジェクトを作成します。サウンドクリップを再生する「on」コールバックを実装します。

この場合、オブザーバーパターンが勝つと思います。第一に、ポーリングはよりプロセッサを集中的に使用します。第二に、車がオンになったときにサウンドクリップがすぐに発射されない。ポーリング期間のため、最大1秒のギャップが生じる可能性があります。


シナリオはほとんど考えられません。オブザーバーパターンは、実際に現実世界と現実世界にマッピングされるものです。したがって、それを使用しないことを正当化するシナリオはないと思います。
サイードネアマティ

ユーザーインターフェースイベント、または一般的なイベントについて話していますか?
ブライアンオークリー

3
この例では、ポーリング/監視の問題は削除されません。単純に下位レベルに渡しました。プログラムは、何らかのメカニズムによって車がオンになっているかどうかを判断する必要があります。
ダンク

回答:


55

たとえば、ドライバーにRPM測定値を表示するために、すべてのエンジンサイクルについて通知を受けたいと想像してください。

オブザーバーパターン:エンジンは、各サイクルのすべてのオブザーバーに「エンジンサイクル」イベントを発行します。イベントをカウントし、RPM表示を更新するリスナーを作成します。

ポーリング: RPMディスプレイは、エンジンサイクルカウンターを定期的にエンジンに要求し、それに応じてRPMディスプレイを更新します。

この場合、オブザーバーパターンはおそらく緩んでいます。エンジンサイクルは高頻度で優先度の高いプロセスであり、ディスプレイを更新するためだけにそのプロセスを遅延または停止させたくないでしょう。また、エンジンサイクルイベントでスレッドプールをスラッシングしたくありません。


PS: 分散プログラミングでもポーリングパターンを頻繁に使用しています。

オブザーバーパターン:プロセスAは、「イベントEが発生するたびに、プロセスAにメッセージを送信する」というメッセージをプロセスBに送信します。

ポーリングパターン:プロセスAは定期的にプロセスBにメッセージを送信し、「前回ポーリングしてからイベントEが発生した場合は、今すぐメッセージを送信してください」と伝えます。

ポーリングパターンにより、もう少しネットワーク負荷が発生します。しかし、オブザーバーパターンには欠点もあります。

  • プロセスAがクラッシュすると、サブスクライブは解除されず、プロセスBはリモートプロセスの障害を確実に検出できない限り(簡単なことではありません)、永遠に通知を送信しようとします。
  • イベントEが非常に頻繁に発生する場合、および/または通知に大量のデータが含まれる場合、プロセスAは処理できないほど多くのイベント通知を受け取る可能性があります。ポーリングパターンを使用すると、ポーリングを調整できます。
  • オブザーバーパターンでは、高負荷によりシステム全体に「波紋」が生じる可能性があります。ブロッキングソケットを使用する場合、これらの波紋は両方の方向に進む可能性があります。

1
いい視点ね。パフォーマンスのためにポーリングする方が良い場合もあります。
ファルコン

1
予想されるオブザーバーの数も考慮事項です。多数のオブザーバーが予想される場合、それらをすべてオブザーバーから更新することがパフォーマンスのボトルネックになる可能性があります。その後、どこかに値を書き込むだけで、「オブザーバー」に必要なときにその値をチェックさせることがはるかに簡単になります。
マルジャンヴェネマ

1
「リモートプロセスの障害を確実に検出できる場合を除き(簡単なことではありません)」... したがって、「何も変更されていない」応答を可能な限り最小化するのが最良の設計です。+1、良い答え。
pdr

2
@Jojo:はい、できますが、ディスプレイに属するポリシーをRPMカウンターに入れています。たぶん、ユーザーは非常に正確なRPM表示を望みます。
ザンリンクス

2
@JoJo:100番目の各イベントを公開するのはハックです。イベントの頻度が常に適切な範囲内にある場合、イベントの処理にエンジンにとってそれほど時間がかからない場合、すべてのサブスクライバーが同等の精度を必要とする場合にのみ、うまく機能します。また、RPMごとに1つのモジュラス操作が必要です(1秒間に数回のポーリング操作を行うよりも(数千RPMを想定した場合)、CPUの作業ははるかに多くなります)。
ニキエ

7

ポーリングは、ポーリングプロセスがポーリングするものよりもかなり遅い場合に優れています。データベースにイベントを書き込む場合、多くの場合、すべてのイベントプロデューサーをポーリングし、最後のポーリング以降に発生したすべてのイベントを収集してから、単一のトランザクションに書き込みます。発生したすべてのイベントを書き込もうとすると、追いつくことができず、入力キューがいっぱいになったときに問題が発生する可能性があります。また、遅延が大きい、または接続のセットアップとティアダウンが高価な疎結合分散システムではより意味があります。ポーリングシステムの記述と理解は簡単ですが、ほとんどの場合、オブザーバーまたはイベント駆動型の消費者は(私の経験では)より良いパフォーマンスを提供するようです。


7

ポーリングは、接続が失敗したり、サーバーが完了したりする可能性がある場合、ネットワークを介して作業するのがはるかに簡単です。なくなった。

ポーリングはUIを常に最新の状態に保ちたい場合にも適していますが、基になるオブジェクトは非常に高速変更されるため、ほとんどのアプリでは1秒間に数回以上UIを更新しても意味がありません。

サーバーが非常に低コストで「変更なし」に応答でき、あまり頻繁にポーリングせず、数千のクライアントポーリングがない場合、ポーリングは実際に非常にうまく機能します。

ただし、「メモリ内」の場合、通常は作業量が最も少ないため、デフォルトでオブザーバパターンを使用します。


5

ポーリングにはいくつかの欠点がありますが、基本的にはすでに質問の中で述べています。

ただし、オブザーバブルをオブザーバーから完全に切り離したい場合は、より良いソリューションになります。ただし、そのような場合に監視されるオブジェクトには、監視可能なラッパーを使用した方がよい場合があります。

オブジェクトインタラクションでオブザーバブルを監視できない場合にのみポーリングを使用します。これは、コールバックを取得できないデータベースなどをクエリする場合によくあります。別の問題としては、同時実行の問題を回避するために、オブジェクトを直接呼び出すよりも、メッセージをポーリングして処理する方が安全な場合が多いマルチスレッドがあります。


なぜポーリングがマルチスレッド化の方が安全であると考えるのか、私にはよくわかりません。ほとんどの場合、これは当てはまりません。ポーリングハンドラーがポーリングリクエストを受信すると、ポーリングされたオブジェクトの状態を把握する必要があり、オブジェクトが更新中の場合、ポーリングハンドラーにとって安全ではありません。リスナーのシナリオでは、プッシャーが一貫した状態にある場合にのみ通知を受け取るため、ポーリングされたオブジェクトのほとんどの同期の問題を回避できます。
ライライアン

4

ポーリングが通知から引き継ぐときの良い例については、オペレーティングシステムのネットワークスタックを参照してください。

Linuxにとって、ネットワークスタックがNAPIを有効にしたとき、それは大したことでした。NAPIは、ドライバーが割り込みモード(通知)からポーリングモードに切り替えることができるネットワークAPIです。

複数のギガビットイーサネットインターフェイスを使用すると、割り込みによってCPUが過負荷になることが多く、システムの動作速度が本来よりも遅くなります。ポーリングでは、ネットワークカードはポーリングされるまでバッファ内のパケットを収集するか、カードがDMAを介してメモリにパケットを書き込みます。次に、オペレーティングシステムの準備ができると、カードのすべてのデータをポーリングし、標準のTCP / IP処理を実行します。

ポーリングモードを使用すると、CPUは無駄な割り込み負荷なしで、最大処理レートでイーサネットデータを収集できます。割り込みモードを使用すると、作業がそれほど忙しくなくても、CPUはパケット間でアイドル状態になります。

秘密は、あるモードから別のモードに切り替えるタイミングです。各モードには利点があり、適切な場所で使用する必要があります。


2

ポーリングが大好きです!私は?はい!私は?はい!私は?はい!私はまだですか?はい!今はどう?はい!

他の人が述べたように、同じ変更されていない状態を何度も戻すためだけにポーリングしている場合、信じられないほど非効率的です。これは、CPUサイクルを燃焼させ、モバイルデバイスのバッテリー寿命を大幅に短縮するためのレシピです。もちろん、必要な速度で新しい意味のある状態を毎回取得するのは無駄ではありません。

しかし、ポーリングが好きな主な理由は、そのシンプルさと予測可能な性質のためです。コードをトレースして、いつ、どこで、どのスレッドで発生するかを簡単に確認できます。理論的には、世論調査がごくわずかな無駄である世界に住んでいたなら(現実はそこから遠く離れているとはいえ)、コードを維持することは大したことではないでしょう。そして、この場合はすべきではないが、パフォーマンスを無視できるかどうかを確認するように、ポーリングとプルの利点があります。

DOS時代にプログラミングを始めたとき、私の小さなゲームはポーリングを中心に展開していました。キーボード割り込みに関連してほとんど理解できなかった本からいくつかのアセンブリコードをコピーし、キーボード状態のバッファを保存するようにしました。この時点で、メインループは常にポーリングされていました。上キーが押されていませんか?いや。上キーが押されていませんか?いや。今はどう?いや。今?はい。さて、プレーヤーを動かします。

信じられないほど無駄ですが、最近のマルチタスクやイベント駆動型プログラミングに比べて、非常に簡単に推論できることがわかりました。いつ、どこで問題が発生するかを正確に知っていたので、フレームレートを安定して予測可能に保つことが、しゃっくりなしに簡単になりました。

それ以来、条件変数を使用してスレッドに通知して新しい状態を取得できるポイントを通知するなど、CPUサイクルを実際に燃やすことなく、その利点と予測可能性を得る方法を常に探しています彼らのことをして、再び通知されるのを待って眠りに戻ります。

そして、どういうわけか、イベントキューは少なくともオブザーバーパターンよりも作業しやすいと思いますが、それでもあなたがどこに行くのか、何が起こるのかを予測するのはそれほど簡単ではありません。少なくともイベント処理制御フローをシステム内のいくつかの重要な領域に集中させ、1つの関数から中央のイベント処理スレッドの外部で突然完全にリモートで予期しない場所にバウンスするのではなく、常に同じスレッドでそれらのイベントを処理します。したがって、二分法は常にオブザーバーとポーリングの間にある必要はありません。イベントキューは一種の妥協点です。

しかし、ええ、どういうわけか、私は何年も前にポーリングしていたときに使用していた予測可能な制御フローに類推的に近いことを行うシステムについて推論するのがはるかに簡単になりますが、状態の変更が発生していない時間。したがって、条件変数のようにCPUサイクルを不必要に燃やさない方法でそれを行うことができれば、その利点があります。

同種ループ

Josh Caswellわかりました、私の答えにいくつかの愚かさを指摘したことから素晴らしいコメントを得ました:

「スレッドに通知するために条件変数を使用するような」ポーリングではなく、イベントベース/オブザーバーの配置のように聞こえる

技術的には、条件変数自体がオブザーバーパターンを適用してスレッドをウェイクアップ/通知するため、「ポーリング」と呼ぶのはおそらく誤解を招く可能性があります。しかし、DOSの日からのポーリングと同様の利点が得られます(制御フローと予測可能性の点で)。もっと説明しようと思います。

当時魅力的だったのは、コードの一部を見たり、トレースしたりして、「さて、このセクション全体がキーボードイベントの処理専用です。このコードのセクションでは他に何も起こらない」ということです。そして、私は前に何が起こるかを正確に知っており、その後に何が起こるかを正確に知っています(物理学やレンダリングなど)。」キーボード状態のポーリングにより、この外部イベントへの応答で何をすべきかを処理する限り、制御フローのそのような集中化が実現しました。この外部イベントにはすぐに応答しませんでした。私たちは都合の良いときにそれに応じました。

オブザーバーパターンに基づいたプッシュベースのシステムを使用すると、多くの場合それらの利点が失われます。コントロールのサイズが変更されると、サイズ変更イベントがトリガーされます。それをトレースすると、より多くのイベントをトリガーするサイズ変更で多くのカスタム処理を行うエキゾチックなコントロールの中にいることがわかります。私たちは、システム内のどこに行き着くのかについて、これらすべてのカスケードイベントをトレースすることに完全に驚きました。さらに、スレッドAはここでコントロールのサイズを変更し、スレッドBは後でコントロールのサイズを変更する可能性があるため、これらのすべてが特定のスレッドで一貫して発生しないこともあります。だから、すべてがどこで起こるのか、何が起こるのかを予測するのがどれほど難しいかを考えると、私はいつもこれを推論するのが非常に難しいと感じました。

イベントキューは、少なくともこれらのすべてのことがスレッドレベルで発生する場所を単純化するため、推論するのが少し簡単です。しかし、多くの異なることが起こっている可能性があります。イベントキューには、処理するイベントの折mixture的な混合物を含めることができますが、各イベントは、発生したイベントのカスケード、処理された順序、およびコードベース内のあらゆる場所でバウンスする方法について、まだ驚かされる可能性があります。

ポーリングに「最も近い」と考えているものは、イベントキューを使用しませんが、非常に均質なタイプの処理を延期します。Aは、PaintSystemそれが適切なzオーダーでその内部の細胞や再描画のすべてを通じて、単純なシーケンシャルループを実行し、その時点でウィンドウの特定のグリッドセルを、再描画するためにやるべき仕事が絵だと条件変数を介して警告を受ける可能性があります。再描画が必要なセルに存在する各ウィジェットでペイントイベントをトリガーするために、ここで間接呼び出し/動的ディスパッチの1つのレベルがあるかもしれませんが、それだけです-間接呼び出しの1つのレイヤーだけです。条件変数は警告するオブザーバーパターン使用していますPaintSystem、それはやるべき仕事を持っていることを、それはより多くのそれよりも何も指定せず、PaintSystemその時点で、1つの均一で非常に均質なタスクに専念しています。PaintSystem'sコードをデバッグおよびトレースしているとき、ペイント以外は何も起こらないことがわかっています。

そのため、イベントキュー処理で得られるような多数の責任を実行する異種のデータ上の非均質ループではなく、非常に特異な責任を適用するデータに対して均質ループを実行するこれらのものがある場所までシステムをダウンさせることです。

私たちはこのタイプのことを目指しています:

when there's work to do:
   for each thing:
       apply a very specific and uniform operation to the thing

とは対照的に:

when one specific event happens:
    do something with relevant thing
in relevant thing's event:
    do some more things
in thing1's triggered by thing's event:
    do some more things
in thing2's event triggerd by thing's event:
    do some more things:
in thing3's event triggered by thing2's event:
    do some more things
in thing4's event triggered by thing1's event:
    cause a side effect which shouldn't be happening
    in this order or from this thread.

などなど。また、タスクごとに1つのスレッドである必要はありません。1つのスレッドは、GUIコントロールのレイアウト(サイズ変更/再配置)ロジックを適用して再描画できますが、キーボードやマウスのクリックを処理できない場合があります。したがって、これはイベントキューの同質性を改善するだけと見なすことができます。ただし、イベントキューを使用して、サイズ変更および描画機能をインターリーブする必要もありません。私たちは次のことができます:

in thread dedicated to layout and painting:
    when there's work to do:
         for each widget that needs resizing/reposition:
              resize/reposition thing to target size/position
              mark appropriate grid cells as needing repainting
         for each grid cell that needs repainting:
              repaint cell
         go back to sleep

したがって、上記のアプローチは、実行する作業があるときにスレッドに通知するために条件変数を使用するだけですが、異なるタイプのイベントをインターリーブしません(1つのループでサイズ変更、別のループでペイント、両方の混合ではありません)実行する必要のある作業が正確に何であるかをわざわざ伝える必要があります(スレッドは、システム全体のECSの状態を調べることで目覚めると「検出」します)。実行される各ループは本質的に非常に均質であり、すべてが発生する順序について簡単に推論できます。

このタイプのアプローチを何と呼ぶべきかわかりません。他のGUIエンジンがこれを行うのを見たことがなく、それは私の独自のエキゾチックなアプローチのようなものです。しかし、オブザーバーまたはイベントキューを使用してマルチスレッドGUIフレームワークを実装しようとする前に、それをデバッグするのは非常に困難でした。ソリューションについて(一部の人はこれを行うことができますが、私は十分にスマートではありません)。私の最初の反復設計では、信号を介してスロットを直接呼び出しましたが、一部のスロットは非同期作業を行うために他のスレッドを生成しました。これは最も推論が難しく、競合状態とデッドロックにつまずいていました。2回目のイテレーションではイベントキューを使用しましたが、これは少し簡単に推論できますが、しかし、あいまいなデッドロックや競合状態に陥ることなく、脳がそれを行うのは簡単ではありません。3回目と最後の反復では、上記のアプローチを使用し、最終的には、私のような愚かな単純なものでも正しく実装できるマルチスレッドGUIフレームワークを作成できました。

次に、このタイプの最終的なマルチスレッドGUI設計により、私が推論するのが非常に簡単で、私が犯しがちだったタイプの間違いを避けることができる他の何かを考え出すことができました。少なくとも、これらの同種のループと、DOS時代にポーリングしていたときと似た制御フローに似ているためです(実際にはポーリングではなく、作業があるときにのみ作業を実行します)。アイデアは、イベント処理モデルから可能な限り遠くに移動することでした。これは、非均一ループ、非均一副作用、非均一制御フローを意味し、均一データで均一に動作し、分離する均一ループにますます取り組むことでした。副作用を「何」に集中しやすくする方法で統一します


1
「スレッドに通知するために条件変数を使用するような」ポーリングではなく、イベントベース/オブザーバーの配置のように聞こえます。
ジョシュキャスウェル

違いは非常に微妙ですが、通知はスレッドを起動するための「やるべき仕事があります」という形になっています。例として、オブザーバーパターンは、親コントロールのサイズを変更すると、動的ディスパッチを使用して階層のサイズ変更呼び出しをカスケードします。物事のサイズ変更イベント関数は、間接的にすぐに呼び出されます。その後、彼らはすぐに自分自身を塗り直すかもしれません。私たちはイベントキューを使用している場合次に、親コントロールのサイズを変更すると、リサイズ延期の方法で呼び出されてしまうかもしれません各コントロールのサイズ変更機能を、その時点での階層ダウンイベント、で...プッシュするかもしれない

...次に、すべてのサイズ変更が完了した後、すべてが中央のイベント処理スレッドから延期された方法で呼び出される再ペイントイベントをプッシュする可能性があることをポイントします。そして、デバッグと少なくとも処理が行われている場所(スレッドを含む)について簡単に推論できる限り、集中化は少し有益であることがわかります...そして、ポーリングに最も近いと考えているのは、これらのソリューション...

たとえば、LayoutSystem通常はスリープしているが、ユーザーがコントロールのサイズを変更すると、条件変数を使用してを起動しLayoutSystemます。次に、LayoutSystem必要なすべてのコントロールのサイズを変更し、スリープ状態に戻ります。このプロセスでは、ウィジェットが存在する長方形の領域は更新が必要としてマークされ、その時点でPaintSystemウェイクアップしてそれらの長方形の領域を通過し、フラットな順次ループで再描画が必要な領域を再描画します。

そのため、条件変数自体はオブザーバーパターンに従ってスレッドを起動するよう通知しますが、「やるべき仕事があります」以上の情報は送信しません。そして、ウェイクアップする各システムは、非常に均質なタスクを適用する非常にシンプルなループで物事を処理することに専念しています。

-4

オブザーバーパターンについての概念的な考え方の概要を説明します。youtubeチャンネルの購読などのシナリオを考えてください。チャンネルにサブスクライブしているユーザーの数があり、多くのビデオで構成されるチャンネルが更新されると、サブスクライバーはこの特定のチャンネルに変更があることを通知されます。そのため、チャネルがSUBJECTの場合、チャネルに登録されているすべてのOBSERVERをサブスクライブ、サブスクライブ解除、および通知することができます。


2
これは、オブザーバパターンを使用するよりもイベントのポーリングの方が優れているのかという質問に対処しようとさえしません。参照してください。回答する方法
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.