web-dev-qa-db-ja.com

Nodejsイベントループ

Nodejsアーキテクチャには内部的に2つのイベントループがありますか?

  • libev/libuv
  • v8 javascriptイベントループ

I/Oリクエストで、ノードはlibeioへのリクエストをキューに入れ、li​​bevを使用してイベントを介してデータの可用性を通知し、最終的にそれらのイベントはコールバックを使用してv8イベントループで処理されますか?

基本的に、libevとlibeioはnodejsアーキテクチャにどのように統合されていますか?

Nodejsの内部アーキテクチャを明確に説明するドキュメントはありますか?

135
Tamil

私はnode.jsとv8のソースコードを個人的に読んでいます。

ネイティブモジュールを作成するためにnode.jsアーキテクチャを理解しようとしたときに、同様の問題が発生しました。

ここに投稿しているのは、node.jsに対する私の理解であり、これも少し軌道に乗っていない可能性があります。

  1. Libev は、単純にイベントループ操作を実行するためにnode.jsで実際に内部的に実行されるイベントループです。もともと* nixシステム用に書かれています。 Libevは、プロセスを実行するためのシンプルかつ最適化されたイベントループを提供します。 libev here の詳細を読むことができます。

  2. LibEio は、入力出力を非同期に実行するライブラリです。ファイル記述子、データハンドラ、ソケットなどを処理します。詳細については、こちらをご覧ください here

  3. LibUv は、libeio、libev、c-ares(DNSの場合)およびiocp(Windows非同期ioの場合)の最上位にある抽象化レイヤーです。 LibUvは、イベントプール内のすべてのioおよびイベントを実行、維持、管理します。 (libeioスレッドプールの場合)。 libUvで Ryan Dahlのチュートリアル を確認してください。 libUvがどのように機能するかについて理解が深まると、libuvおよびv8上でnode.jsがどのように機能するかが理解できます。

Javascriptイベントループだけを理解するには、これらのビデオの視聴を検討する必要があります。

非同期モジュールを作成するためにnode.jsでlibeioがどのように使用されるかを確認するには、 この例 を参照してください。

基本的に、node.js内で行われるのは、v8ループが実行され、すべてのjavascriptパーツとC++モジュールを処理することです(メインスレッドで実行されている場合(公式ドキュメントによると、node.js自体はシングルスレッドです))。メインスレッドの外部では、libevとlibeioがスレッドプールでそれを処理し、libevはメインループとの相互作用を提供します。私の理解では、node.jsには1つの永続的なイベントループがあります。それはv8イベントループです。 C++非同期タスクを処理するために、スレッドプールを使用しています(libeioおよびlibev経由)。

例えば:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

すべてのモジュールに表示されるのは、通常、スレッドプールで関数Taskを呼び出すことです。完了すると、メインスレッドでAfterTask関数を呼び出します。一方、Eio_REQUESTは、スレッドプールとメインスレッド間の通信を提供することを目的とする構造体/オブジェクトである可能性のあるリクエストハンドラです。

169
ShrekOverflow

議論されたエンティティの一部(例:libevなど)はしばらく前から関連性を失っているように見えますが、この質問にはまだ大きな可能性があると思います。

今日のノードのコンテキストでの抽象的なUNIX環境での抽象的な例を使用して、イベント駆動モデルの動作を説明してみましょう。

プログラムの観点:

  • スクリプトエンジンは、スクリプトの実行を開始します。
  • CPUにバインドされた操作が検出されるたびに、完全にインライン(実マシン)で実行されます。
  • I/Oバウンド操作が検出されるたびに、要求とその完了ハンドラーが「イベントマシン」(仮想マシン)に登録されます
  • スクリプトが終了するまで、上記と同じ方法で操作を繰り返します。 CPUバウンド操作-インラインのI/Oバウンド操作を実行し、上記のように機械に要求します。
  • I/Oが完了すると、リスナーがコールバックされます。

上記のイベントマシンは、libuv AKAイベントループフレームワークと呼ばれます。 Nodeこのライブラリを活用して、イベント駆動プログラミングモデルを実装します。

ノードの視点:

  • ランタイムをホストするスレッドが1つあります。
  • ユーザースクリプトを取得します。
  • ネイティブにコンパイル[v8を活用]
  • バイナリをロードし、エントリポイントにジャンプします。
  • コンパイルされたコードは、プログラミングプリミティブを使用して、CPUにバインドされたアクティビティをインラインで実行します。
  • 多くのI/Oおよびタイマー関連のコードにはネイティブラップがあります。たとえば、ネットワークI/O。
  • そのため、I/OコールはスクリプトからC++ブリッジにルーティングされ、I/Oハンドルと完了ハンドラーが引数として渡されます。
  • ネイティブコードはlibuvループを実行します。ループを取得し、I/Oを表す低レベルイベントをキューに入れ、ネイティブコールバックラッパーをlibuvループ構造に入れます。
  • ネイティブコードがスクリプトに戻ります-現時点ではI/Oは行われません!
  • 上記の項目は、すべての非I/Oコードが実行され、すべてのI/Oコードが登録されるまで何度も繰り返されます。
  • 最後に、システムに実行するものが残っていない場合、ノードはlibuvに制御を渡します
  • libuvは動作を開始し、登録されているすべてのイベントを取得し、オペレーティングシステムにクエリを実行して操作性を取得します。
  • 非ブロッキングモードでI/Oの準備ができているものは、ピックアップされ、I/Oが実行され、コールバックが発行されます。次々。
  • まだ準備が整っていないもの(たとえば、他のエンドポイントがまだ何も書き込んでいないソケット読み取り)は、使用可能になるまでOSでプローブされ続けます。
  • ループは内部的に、増え続けるタイマーを維持します。アプリケーションが遅延コールバック(setTimeoutなど)を要求すると、この内部タイマー値を利用して、コールバックを起動する適切な時間を計算します。

ほとんどの機能はこの方法で提供されますが、ファイル操作の一部(非同期バージョン)は、libuvに統合された追加のスレッドの助けを借りて実行されます。ネットワークI/O操作は、データなどで応答する他のエンドポイントなどの外部イベントを予期して待機できますが、ファイル操作にはノード自体からの何らかの作業が必要です。たとえば、ファイルを開いてfdがデータの準備が整うまで待つと、実際には誰も読んでいないので、それは起こりません!同時に、メインスレッドのインラインファイルから読み取る場合、プログラム内の他のアクティビティをブロックする可能性があり、CPUバインドアクティビティと比較してファイル操作が非常に遅いため、目に見える問題を引き起こす可能性があります。そのため、ファイルの操作には内部ワーカースレッド(UV_THREADPOOL_SIZE環境変数で構成可能)が採用されますが、プログラムの観点からは、イベントドリブンの抽象化はそのまま機能します。

お役に立てれば。

20

libuvの概要

node.js プロジェクトは、ブラウザから切り離されたJavaScript環境として2009年に始まりました。 Googleの V8 とMarc Lehmannの libev を使用して、node.jsはI/Oのモデル(イベントあり)をプログラミングスタイルに適した言語と組み合わせました。ブラウザによって形作られていた方法のため。 node.jsの人気が高まるにつれて、Windowsで動作させることが重要でしたが、libevはUnixでのみ実行されました。 Windowsでのkqueueや(e)pollなどのカーネルイベント通知メカニズムに相当するものはIOCPです。 libuvは、プラットフォームに応じてlibevまたはIOCPを抽象化したものであり、libevに基づくAPIをユーザーに提供します。 node-v0.9.0バージョンのlibuv libevは削除されました

また、Node.jsのイベントループを@ BusyRich で説明する1つの図


2017年5月9日更新

このドキュメントごとに Node.jsイベントループ

次の図は、イベントループの操作の順序の簡単な概要を示しています。

_┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘_

注:各ボックスは、イベントループの「フェーズ」と呼ばれます。

フェーズの概要

  • timers:このフェーズは、setTimeout()およびsetInterval()によってスケジュールされたコールバックを実行します。
  • I/Oコールバック:を除くほとんどすべてのコールバックを実行します
  • コールバックを閉じる、タイマーによってスケジュールされたもの、およびsetImmediate()。アイドル、準備:内部でのみ使用されます。
  • poll:新しいI/Oイベントを取得します。適切な場合、ノードはここでブロックします。
  • checksetImmediate()コールバックはここで呼び出されます。
  • コールバックを閉じる:例socket.on('close', ...)

Node.jsは、イベントループの各実行の間に、非同期I/Oまたはタイマーを待機しているかどうかを確認し、待機していない場合は完全にシャットダウンします。

17
zangw

NodeJsアーキテクチャには1つのイベントループがあります。

Node.jsイベントループモデル

ノードアプリケーションは、シングルスレッドのイベント駆動型モデルで実行されます。ただし、Nodeは、作業を実行できるようにバックグラウンドでスレッドプールを実装します。

Node.jsはイベントキューに作業を追加し、イベントループを実行する単一のスレッドにそれを取得させます。イベントループは、イベントキューの一番上のアイテムを取得して実行し、次のアイテムを取得します。

より長く存続するコードまたはI/Oをブロックするコードを実行する場合、関数を直接呼び出す代わりに、関数の完了後に実行されるコールバックとともに関数をイベントキューに追加します。 Node.jsイベントキューのすべてのイベントが実行されると、Node.jsアプリケーションは終了します。

アプリケーション関数がI/Oをブロックすると、イベントループが問題を引き起こし始めます。

Node.jsは、イベントコールバックを使用して、I/Oのブロックを待つ必要を回避します。したがって、ブロッキングI/Oを実行する要求は、バックグラウンドで別のスレッドで実行されます。

I/Oをブロックするイベントがイベントキューから取得されると、Node.jsはスレッドプールからスレッドを取得し、メインイベントループスレッドではなくそこでスレッドの関数を実行します。これにより、ブロッキングI/Oがイベントキュー内の残りのイベントを保持するのを防ぎます。

13
Peter Hauge

Libuvが提供するイベントループは1つだけです。V8は単なるJSランタイムエンジンです。

8
Warren Zhou

pbkdf2関数にはJavaScript実装がありますが、実際には実行するすべての作業をC++側に委任します。

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

リソース: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Libuvモジュールには、標準ライブラリの非常に特定の機能に関連する別の責任があります。

一部の標準ライブラリ関数呼び出しでは、Node C++側とLibuvは、イベントループの外部で高価な計算を行うことにします。

代わりに、スレッドプールと呼ばれるものを使用します。スレッドプールは、pbkdf2関数などの計算負荷の高いタスクの実行に使用できる一連の4つのスレッドです。

デフォルトでは、Libuvはこのスレッドプールに4つのスレッドを作成します。

イベントループで使用されるスレッドに加えて、アプリケーション内で発生する必要がある高価な計算をオフロードするために使用できる4つのスレッドがあります。

Node標準ライブラリに含まれる多くの関数は、このスレッドプールを自動的に使用します。pbkdf2関数はその1つです。

このスレッドプールの存在は非常に重要です。

したがって、Nodeは、シングルスレッドではありません。これは、計算コストのかかるタスクを行うためにNodeが使用する他のスレッドがあるためです。

イベントプールが計算負荷の高いタスクを実行する責任がある場合、Nodeアプリケーションは他に何もできません。

CPUは、スレッド内のすべての命令を1つずつ実行します。

スレッドプールを使用すると、計算の実行中にイベントループ内で他のことを実行できます。

0
Daniel

Javascript初心者として、NodeJSには2つのイベントループが含まれているのかという疑問もありました。長い調査とV8の貢献者の1人との議論の後、私は次の概念を得ました。

  • イベントループは、JavaScriptプログラミングモデルの基本的な抽象概念です。そのため、V8エンジンは、イベントループのデフォルトの実装を提供します。どの埋め込み機能(ブラウザ、ノード)が置換または拡張できるか。イベントループのV8デフォルト実装を見つけることができます here
  • NodeJSには、ノードランタイムによって提供されるイベントループが1つだけ存在します。 V8のデフォルトのイベントループ実装は、NodeJSイベントループ実装に置き換えられました。
0
arunjos007