ツールを知る:libuvのイベントループ

画像

ユーデルパン。 時計師。 1924



「コンピューターはステートマシンです。 ストリーミングプログラミングは、有限状態マシンのプログラミング方法がわからない人に必要です。

アラン・コックス、約 ウィキペディア




「ツールを知って」-誰もが繰り返し信頼します。 モジュールを信頼し、フレームワークを信頼し、他の例を信頼します。



Node.jsインタビューでのお気に入りの質問は、 イベントループデバイスです。 そして、この知識がアプリケーション開発者にとって有用であることは明らかであるため、自分でイベントサイクルのデバイスに突入しようとする人はほとんどいません。 基本的に、誰もが上の写真に満足しています。 これは、あなたが見たことのない映画の改作に似ていますが、友人があなたに言ったことについてです。



画像



おそらく私にとって最も難しいことは、自分の間違いを認め、何かを知らないことに同意することです。 間違いについて話したり書いたりするのは好きではありません。 基本的に、誰もが自分の成功と良い話について書いて話すのが好きで、人は無敵のヒーローのイメージを構築しようとします。

しかし、原則として、間違いは、誰かが提起された質問を研究するのに必要な時間よりも短い時間を費やしたという事実のために、まさに表面的な判断のために、無知から正確に行われます。 証拠。 知ってるよ。



以下では、 libuvソースコードの例を使用してイベントサイクルについての私の理解を説明しようとします(V8と連携して、これはNode.jsの基礎です)



ちなみに、後者は、現代の現実では、困難な課題になっています。 現時点では、 npmだけが約50万個のモジュールをカウントしているだけで、 githubのリポジトリの軍隊についても話していません。 しかし、これは、所定の位置にとどまるためにどのように機能するかであり、実行する必要があり、移動するには、2倍の速度で実行する必要があります。



このメモは主に私に思い出させるものであり、より注意を払うことを思い出させるものです。 読者には、自分でソースコードに飛び込み、結論を出してからこのテキストに戻ることをお勧めします。



Node.jsの内部で実際に発生することの大まかな近似も以下に説明します。 とりわけ、このノートはlibuvのソースコードに正確に基づいています。 私は、UNIXパートのライブラリのコードベースを検討します 。 勝つためのコードは異なります。



まあ、最初は少し基本的な用語:



イベント駆動プログラミング (SOP、イベント駆動プログラミング/ EDP)は、プログラムの実行がイベントによって決定されるプログラミングパラダイムです。



SOPパラダイムはGUIの開発で積極的に使用されていますが、サーバー側でも使用されていました。 1999年、当時人気のあったSimtelパブリックFTPサーバーにサービスを提供していた管理者のDen Kegelは 、ギガビットチャネル上のノードがハードウェアの観点から10,000接続の負荷を処理できるはずであることに気付きましたが、ソフトウェアではこれを許可していませんでした。 この問題は 、それぞれが個別の接続で作成された多数のプログラムスレッドに関連していました。



単一のスレッドで動作するイベントループのアイデアは、この問題を解決しました。 JavaScriptの世界(Node.js)だけでなく、同様の実装が存在します。 たとえば、PythonのAsyncioTwisted 、RubyのEventMachineCelluloid 、JavaのVert.xなどです。 このような実装のもう1つの明るい代表は、 Nginxプロキシサーバーです。



SOPの中心にあるのは、プログラム内のイベントとメッセージを管理するソフトウェア設計であるイベントループです。



サイクルは、 非同期入力/出力 、または非ブロッキング入力/出力で動作します 。これは、転送が完了する前に他のプロセスが実行を継続できるデータ処理の形式です。



コールバック関数 -実行可能コードを別のコードのパラメーターの1つとして転送する機能 。 同様の手法により、非同期の入出力を便利に処理できます。



「Hello World!」



それでは、公式の「Hello World!」 http://docs.libuv.orgの例から始めましょう。



画像



この例は単純で、必要なRAMが予約され、イベントループの構造が初期化され、デフォルトモードで起動します (ちなみに、これはNode.jsで使用されるモードです)。



その後、ループが閉じ(すべてのイベントオブザーバー、 シグナルオブザーバーを停止し、オブザーバーに割り当てられたメモリを解放します)、サイクル自体によって予約されたメモリを解放します。 ループスタート関数( uv_run )のデバイスに興味があります。そのソースコードを見てみましょう(完全にオリジナルではないため、デフォルトモードに関係しない行を削除したため、「 mode 」変数はどこにも関与しません)。



画像



ご覧のように、起動関数の本体は「 whileループではなく、 uv__loop_aliveの呼び出しで始まります。 次に、この関数はアクティブなハンドラーまたはリクエストをチェックします



画像



この関数の実行結果は、 whileループが開始するかどうかを決定します 。 要求またはハンドラがない場合、開始関数はイベントループの実行時間を更新し、すぐに終了します。

処理するものがあり( r!= 0 )、停止フラグが設定されていない場合( stop_flag == 0 )、サイクルが開始されます。 また、ループ反復の最初のアクションは、ランタイムの更新( uv__update_time )です。



画像



画像



反復の次のステップは、タイマーを開始することです。



画像



イベントループの構造には、いわゆるタイマーの束が含まれています。 タイマー開始関数は、最短時間でヒープからタイマーハンドラーを引き出し、この値をイベントループの実行時間と比較します。 タイマーが短い場合、このタイマーは停止します(ヒープから削除され、そのハンドラーもヒープから削除されます)。 次に、再起動する必要があるかどうかを確認します。



Node.js(JavaScript)には、 setIntervalおよびsetTimeout関数があります 。libuvに関しては、これは同じことです- タイマー( uv_timer_tで、唯一の違いは、間隔フラグに再試行フラグ( repeat = 1 )があることです。



興味深い観察:再試行フラグが設定されている場合、uv_timer_stop関数はタイマーハンドラーに対して2回機能します。



イベントループの繰り返しの次のステップ、つまり保留中のコールバックを起動する機能に進みましょう。 呼び出しはキューに入れられます。 これらは、Unixのすべてにfileがあるため、タイプは実際には重要ではないため、ファイル、 TCPまたはUDP接続、一般にすべてのI / O操作の読み取りまたは書き込みのハンドラーになります



画像



画像



反復の次は、2つの神秘的な行です。



画像



これらは実際にはコールバックをトリガーする関数でもありますが、I / Oとは関係ありません。 実際、これらは外部操作(I / Oを意味する)の実行を開始する前に完了するのが良いと思われる内部準備アクションの一種です。 「Hello World」の場合、そのようなハンドラーはありませんが、そのようなコールバックが登録されているサイトには例があります。



画像



この例では、アイドルハンドラーは何もせず、カウンターが特定の値に達するまで実行されます。 プリプロセッサも同じ方法で登録されます。



Node.js(JavaScript)にはこれらのハンドラーに相当するものはありません。 これらの手順のいずれか1つで実行される何らかのコールバックを登録することはできません。 ただし、 process.nextTickを使用し 1つの警告を作成する必要があります。この関数はイベントサイクルの現在の段階で直接機能するため、これらのステップの1つでコードを不注意に実行できます。 process.nextTick関数自体は、libuvライブラリとは関係ありません。



このトピック( process.nextTickの作業)について、私はまだ古いが、まだ関連性のあるstackoverflowの図を持っています:



画像



反復の次の興味深いステップは、外部I / O操作です( poll(2) )。



ここでは、外部操作を実行する時間の計算と、直接、外部操作の2つのステップを組み合わせました。



画像



実装による外部I / O操作の実行時間の計算は、この時間の値が最も近いタイマーに基づいて計算されるため、タイマー開始関数に似ています。 ちなみに、これは非ブロックモデル( 非ブロックポーリング )で実現されます。



画像



画像



uv__io_poll関数のソースコード非常に複雑であり、小さくはありません。 マルチスレッドの作業、イベントオブザーバー、コールバックが記録され、 ファイル記述子使用して作業が行われます



ここでは、この関数のコードは提供しません。図は、この操作の本質を完全に反映しています。



画像



画像



イベントループ反復コマンドキューの次の操作はuv__run_checkです。 これは、関数uv__run_idleおよびuv__run_prepareと本質的に同一です。 これは、同じ原則で登録し、外部操作後に呼び出すコールバックの起動です。 ただし、この場合、Node.jsからそのようなハンドラーを登録する機会があります。 これはsetImmediate関数です(つまり、外部I / O操作後の即時実行)。



最後から2番目のステップは、終了ハンドラーを起動することです。



この関数は、クローズハンドラーのリンクリストをバイパスし、それぞれのクローズを完了しようとします。 ハンドラーに閉じる特別なコールバックがある場合、最後に、このコールバックが起動されます。



画像



画像



そして、反復の最後のステップ、これはおなじみのuv__loop_alive関数です。 この関数がゼロ以外の結果を返す場合、イベントループは新しい反復を開始します。



***



コメントや追加がある場合は、コメントでそれらを確認するか、 artur.basak.devingrodno @ gmail.comに連絡してください。



便利なリンク
LibUV:設計の概要

Nodejs.org:Node.jsイベントループ、タイマー、およびprocess.nextTick()

トピックに関するNodejs.orgドキュメントの翻訳

フィリップ・ロバーツ:とにかくイベントループとは一体何ですか?

RisingStack.com:Node.jsイベントループについて

Nodesource.com:Node.jsイベントループについて

Mozilla.org:EventLoop




All Articles