Nodejsイベントループ


141

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

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

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

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

nodejsの内部アーキテクチャを明確に示すために利用できるドキュメントはありますか?

回答:


175

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

ネイティブモジュールを作成するためにnode.jsアーキテクチャを理解しようとしたときに、私はあなたと同じような問題に遭遇しました。

私がここに投稿しているのは、node.jsに対する私の理解であり、これも少しずれているかもしれません。

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

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

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

JavaScriptイベントループだけを理解するには、これらのビデオを視聴することを検討してください。

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

基本的に、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、スレッドプールとメインスレッド間の通信を提供することを目的とする構造体/オブジェクトであるリクエストハンドラーがあります。


libuvが内部でlibevを使用するという事実に依存することは、コードをクロスプラットフォームにしないための良い方法です。libuvの公開インターフェースのみに注意する必要があります。
レイノス

1
@Raynos libuvは、そのx-platfousing複数のライブラリを確認することを目指しています。正しい ?したがって、libuvを使用することをお勧めします
ShrekOverflow

1
@Abhishek From Doc- process.nextTickイベントループの次のループで、このコールバックを呼び出します。これはsetTimeout(fn、0)の単純なエイリアスではなく、はるかに効率的です。これはどのイベントループを指しますか?V8イベントループ?
タミル語

2
libuvがlibevの上に実装されなくなったことに注意してください。
strcat 2013

4
このイベントキューを「確認」する方法はありますか?スタック上の呼び出しの順序を確認し、何が起こっているのかをよりよく理解するためにそこにプッシュされる新しい関数を確認したいのですが...イベントキューに何がプッシュされたかを示す変数がありますか?
tbarbe 2013

20

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

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

プログラムの視点:

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

上記のイベントメカニズムはlibuv AKAイベントループフレームワークと呼ばれます。ノードはこのライブラリを利用して、イベント駆動型プログラミングモデルを実装します。

ノードの視点:

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

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

お役に立てれば。


これらのことをどのようにして知りましたか、出典を教えていただけますか?
Suraj Jain

18

libuvの概要

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

@ BusyRichによる Node.jsのイベントループを説明する1つの画像


2017年5月9日更新

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

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

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

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

フェーズの概要

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

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


あなたはその " In the node-v0.9.0 version of libuv libev was removed" を引用しましたが、nodejsにはそれについての説明がありませんchangeloggithub.com/nodejs/node/blob/master/CHANGELOG.md。そして、libevが削除された場合、nodejsでどのように非同期I / Oが実行されているのでしょうか?
intekhab 2016年

@intekhab、このリンクごとに、libeioに基づくlibuvはnode.jsのイベントループとして使用できると思います。
zangw 2016年

@intekhab私はlibuvがI / Oとポーリングに関連するすべての機能を実装していると思います。ここでこのドキュメントを確認してください:docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

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がイベントキュー内の残りのイベントを保持することが防止されます。



1

JavaScriptの初心者として、私も同じ疑問を持っていました。NodeJSには2つのイベントループが含まれていますか?長い研究とV8の貢献者の1人との議論の後、私は次の概念を得ました。

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

0

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関数。

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

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

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

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

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.