web-dev-qa-db-ja.com

イベントループは、ポーリングが最適化されたfor / whileループだけですか?

イベントループとは何かを理解しようとしています。多くの場合、説明は、イベントループでは、イベントが発生したことを通知されるまで何かを行うというものです。その後、イベントを処理し、以前と同じように作業を続けます。

上記の定義を例にマッピングします。イベントループで「リッスン」するサーバーがあり、ソケット接続が検出されると、サーバーからのデータが読み取られて表示され、その後サーバーは以前と同じようにリスニングを再開/開始します。


ただし、このイベントが発生し、「そのように」通知されることは、私が処理するのに大いに役立ちます。 「イベントリスナーを登録する必要があるのは、まさにそのようなものではありません」と言うことができます。しかし、イベントリスナーとは何か、何らかの理由で返されない関数です。それはそれ自体のループにあり、イベントが発生したときに通知されるのを待っていますか?イベントリスナーもイベントリスナーを登録する必要がありますか?それはどこで終わりますか?


イベントは使用するのに最適な抽象概念ですが、単なる抽象概念です。結局、投票は避けられないと思います。おそらく、私たちはコードでそれを行っていませんが、下位レベル(プログラミング言語の実装またはOS)がそれを行っています。

それは基本的に次の疑似コードに帰着します。これは十分に低い場所で実行されているため、ビジー待機を引き起こしません。

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

これが全体の考えの私の理解であり、これが正しいかどうか聞きたいです。私は全体の考えが根本的に間違っていることを受け入れることにオープンです。その場合、私は正しい説明をお願いします。

57

イベントの準備ができていない場合、ほとんどのイベントループは一時停止します。つまり、オペレーティングシステムは、イベントが発生するまでタスクに実行時間を与えません。

イベントはキーが押されているとしましょう。オペレーティングシステムのどこかにキー操作をチェックするループがあるかどうかを尋ねるかもしれません。答えはノーだ。キーが押されると interrupt が生成され、ハードウェアによって非同期に処理されます。タイマー、マウスの動き、パケットの到着なども同様です。

実際、ほとんどのオペレーティングシステムでは、イベントのポーリングは抽象化です。ハードウェアとOSはイベントを非同期に処理し、アプリケーションがポーリングできるキューに入れます。実際のポーリングは、組み込みシステムのハードウェアレベルでしか見られません。

56
Karl Bielefeldt

イベントリスナーは、独自のループを実行する関数ではなく、最初のランナーが開始ガンを待つリレーレースと考えています。ポーリングの代わりにイベントを使用する主な理由は、CPUサイクルを使用するとイベントの効率が上がるためです。どうして?ハードウェアの上から(ソースコードの下からではなく)見てください。

Webサーバーについて考えてみましょう。サーバーがlisten()を呼び出してブロックすると、コードがリレーランナーとして機能します。新しい接続の最初のパケットが到着すると、ネットワークカードはオペレーティングシステムを中断することにより、レースを開始します。 OSは、パケットを取得する割り込みサービスルーチン(ISR)を実行します。 ISRは、接続を確立する上位レベルのルーチンにバトンを渡します。接続が有効になると、そのルーチンはバトンをlisten()に渡し、バトンをコードに渡します。その時点で、接続を使用して必要な操作を実行できます。私たちが知っているすべてのことについて、レース間では、各リレーランナーがパブに行く可能性があります。イベント抽象化の長所は、コードが気にする必要がないことです。

一部のオペレーティングシステムには、レースのその部分を実行し、バトンを渡し、次のレースの開始を待つために開始点にループバックするイベント処理コードが含まれています。その意味で、イベント処理は、多数の同時ループにおける最適化されたポーリングです。ただし、プロセスを開始する外部トリガーは常に存在します。イベントリスナーは返されない関数ではなく、実行する前にその外部トリガーを待機している関数です。のではなく:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

私はこれを次のように考えています:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

signalと次にハンドラーが実行されるとき、概念的にはコードの実行やループはありません。

13
cxw

いいえ。「最適化されたポーリング」ではありません。 イベントループは、ポーリングの代わりに割り込み駆動I/Oを使用します。

While、Until、Forなどのループはポーリングループです。

「ポーリング」とは、何かを繰り返しチェックするプロセスです。ループコードは継続的に実行され、これは小さな「タイトな」ループであるため、プロセッサがタスクを切り替えて他のことを行う時間はほとんどありません。ほとんどすべての「ハング」、「フリーズ」、「ロックアップ」、またはコンピューターが応答しなくなったときに呼び出したいものは、意図しないポーリングループでコードがスタックしていることを示しています。インストルメンテーションは100%のCPU使用率を示します。

割り込み駆動型イベントループは、ポーリングループよりもはるかに効率的です。ポーリングはCPUサイクルの非常に無駄な使用であるため、それを排除または最小化するためにあらゆる努力が払われます。

ただし、コード品質を最適化するために、ほとんどの言語は、プログラム内で機能的に同様の目的を果たしているため、イベント処理コマンドに対してポーリングループパラダイムをできるだけ使用しようとしています。したがって、ポーリングはキー入力などを待つためのより身近な方法ですが、経験の浅い人がそれを使用して、それ自体で正常に動作するプログラムを試してみるのは簡単ですが、実行中は他に何も動作しません。マシンを「引き継いだ」。

他の回答で説明されているように、割り込み駆動型のイベント処理では、本質的に「フラグ」がCPU内に設定され、そのフラグが他のプロセス(キーボードなど)によって変更されるまでプロセスは「一時停止」されます(実行は許可されません)。ドライバがユーザーがキーを押したときにそれを変更します)。フラグが、ラインが「プルされた」などの実際のハードウェア状態である場合、「割り込み」または「ハードウェア割り込み」と呼ばれます。ただし、ほとんどは、CPUまたはメインメモリ(RAM)の単なるメモリアドレスとして実装され、「セマフォ」と呼ばれます。

セマフォはソフトウェアの制御下で変更できるため、ソフトウェアプロセス間で非常に高速でシンプルな信号メカニズムを提供できます。

ただし、割り込みはハードウェアによってのみ変更できます。割り込みの最も一般的な使用方法は、内部クロックチップによって定期的にトリガーされるものです。クロック割り込みによってアクティブ化される無数の種類のソフトウェアアクションの1つは、セマフォの変更です。

私は多くのことを省きましたが、どこかで止めなければなりませんでした。詳細が必要かどうかお問い合わせください。

8
DocSalvager

通常、答えはハードウェアであり、制御できないOSとバックグラウンドスレッドが共謀して簡単に見えるようにします。ネットワークカードは、CPUに知らせるために interrupt を発生させるデータを受信します。 OSの割り込みハンドラがそれを処理します。次に、制御できないバックグラウンドスレッド(イベントへの登録によって作成され、イベントへの登録以降ずっとスリープ状態になっている)が、イベントの処理の一部としてOSによって起動され、イベントハンドラーを実行します。

7
stonemetal

これまでに見た他のすべての答えに反対して、"yes"と言います。他の答えは複雑すぎます。概念的な観点から見ると、すべてのイベントループは基本的に次のとおりです。

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

イベントループを初めて理解しようとしている場合は、それらを単純なループとして考えても害はありません。基盤となるいくつかのフレームワークは、OSがイベントを配信するのを待っており、イベントを1つ以上のハンドラーにルーティングしてから、次のイベントを待ちます。アプリケーションソフトウェアの観点からは、これですべてです。

7
Bryan Oakley

すべてのイベントトリガーがループで処理されるわけではありません。私が自分のイベントエンジンを頻繁に記述する方法は次のようになります。

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

メモ1はループ上にありますが、ループは各リスナーに通知するためのものです。イベントトリガー自体は必ずしもループ内にあるとは限りません。

理論的には、OSが何らかのregisterListener AP​​Iを公開していれば、OSレベルのキーイベントでも同じ手法を使用できます(代わりにポーリングを頻繁に行うと思いますか?ここでは推測しているだけです)。

1
Thomas Eding