Nodejsアーキテクチャには内部的に2つのイベントループがありますか?
I/Oリクエストで、ノードはlibeioへのリクエストをキューに入れ、libevを使用してイベントを介してデータの可用性を通知し、最終的にそれらのイベントはコールバックを使用してv8イベントループで処理されますか?
基本的に、libevとlibeioはnodejsアーキテクチャにどのように統合されていますか?
Nodejsの内部アーキテクチャを明確に説明するドキュメントはありますか?
私はnode.jsとv8のソースコードを個人的に読んでいます。
ネイティブモジュールを作成するためにnode.jsアーキテクチャを理解しようとしたときに、同様の問題が発生しました。
ここに投稿しているのは、node.jsに対する私の理解であり、これも少し軌道に乗っていない可能性があります。
Libev は、単純にイベントループ操作を実行するためにnode.jsで実際に内部的に実行されるイベントループです。もともと* nixシステム用に書かれています。 Libevは、プロセスを実行するためのシンプルかつ最適化されたイベントループを提供します。 libev here の詳細を読むことができます。
LibEio は、入力出力を非同期に実行するライブラリです。ファイル記述子、データハンドラ、ソケットなどを処理します。詳細については、こちらをご覧ください here 。
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
は、スレッドプールとメインスレッド間の通信を提供することを目的とする構造体/オブジェクトである可能性のあるリクエストハンドラです。
議論されたエンティティの一部(例:libevなど)はしばらく前から関連性を失っているように見えますが、この質問にはまだ大きな可能性があると思います。
今日のノードのコンテキストでの抽象的なUNIX環境での抽象的な例を使用して、イベント駆動モデルの動作を説明してみましょう。
プログラムの観点:
上記のイベントマシンは、libuv AKAイベントループフレームワークと呼ばれます。 Nodeこのライブラリを活用して、イベント駆動プログラミングモデルを実装します。
ノードの視点:
ほとんどの機能はこの方法で提供されますが、ファイル操作の一部(非同期バージョン)は、libuvに統合された追加のスレッドの助けを借りて実行されます。ネットワークI/O操作は、データなどで応答する他のエンドポイントなどの外部イベントを予期して待機できますが、ファイル操作にはノード自体からの何らかの作業が必要です。たとえば、ファイルを開いてfdがデータの準備が整うまで待つと、実際には誰も読んでいないので、それは起こりません!同時に、メインスレッドのインラインファイルから読み取る場合、プログラム内の他のアクティビティをブロックする可能性があり、CPUバインドアクティビティと比較してファイル操作が非常に遅いため、目に見える問題を引き起こす可能性があります。そのため、ファイルの操作には内部ワーカースレッド(UV_THREADPOOL_SIZE環境変数で構成可能)が採用されますが、プログラムの観点からは、イベントドリブンの抽象化はそのまま機能します。
お役に立てれば。
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 │ └───────────────────────┘
_
注:各ボックスは、イベントループの「フェーズ」と呼ばれます。
フェーズの概要
setTimeout()
およびsetInterval()
によってスケジュールされたコールバックを実行します。setImmediate()
。アイドル、準備:内部でのみ使用されます。setImmediate()
コールバックはここで呼び出されます。socket.on('close', ...)
。Node.jsは、イベントループの各実行の間に、非同期I/Oまたはタイマーを待機しているかどうかを確認し、待機していない場合は完全にシャットダウンします。
NodeJsアーキテクチャには1つのイベントループがあります。
ノードアプリケーションは、シングルスレッドのイベント駆動型モデルで実行されます。ただし、Nodeは、作業を実行できるようにバックグラウンドでスレッドプールを実装します。
Node.jsはイベントキューに作業を追加し、イベントループを実行する単一のスレッドにそれを取得させます。イベントループは、イベントキューの一番上のアイテムを取得して実行し、次のアイテムを取得します。
より長く存続するコードまたはI/Oをブロックするコードを実行する場合、関数を直接呼び出す代わりに、関数の完了後に実行されるコールバックとともに関数をイベントキューに追加します。 Node.jsイベントキューのすべてのイベントが実行されると、Node.jsアプリケーションは終了します。
アプリケーション関数がI/Oをブロックすると、イベントループが問題を引き起こし始めます。
Node.jsは、イベントコールバックを使用して、I/Oのブロックを待つ必要を回避します。したがって、ブロッキングI/Oを実行する要求は、バックグラウンドで別のスレッドで実行されます。
I/Oをブロックするイベントがイベントキューから取得されると、Node.jsはスレッドプールからスレッドを取得し、メインイベントループスレッドではなくそこでスレッドの関数を実行します。これにより、ブロッキングI/Oがイベントキュー内の残りのイベントを保持するのを防ぎます。
Libuvが提供するイベントループは1つだけです。V8は単なるJSランタイムエンジンです。
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つずつ実行します。
スレッドプールを使用すると、計算の実行中にイベントループ内で他のことを実行できます。
Javascript初心者として、NodeJSには2つのイベントループが含まれているのかという疑問もありました。長い調査とV8の貢献者の1人との議論の後、私は次の概念を得ました。