回答:
私は個人的にnode.js&v8のソースコードを読んでいます。
ネイティブモジュールを作成するためにnode.jsアーキテクチャを理解しようとしたときに、私はあなたと同じような問題に遭遇しました。
私がここに投稿しているのは、node.jsに対する私の理解であり、これも少しずれているかもしれません。
Libevは、単純なイベントループ操作を実行するために実際にnode.jsで内部的に実行されるイベントループです。もともとは* nixシステム用に書かれています。Libevは、プロセスを実行するためのシンプルで最適化されたイベントループを提供します。libevの詳細については、こちらをご覧ください。
LibEioは非同期で入出力を実行するライブラリです。ファイル記述子、データハンドラー、ソケットなどを処理します。詳細については、こちらをご覧ください。
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
、スレッドプールとメインスレッド間の通信を提供することを目的とする構造体/オブジェクトであるリクエストハンドラーがあります。
process.nextTick
イベントループの次のループで、このコールバックを呼び出します。これはsetTimeout(fn、0)の単純なエイリアスではなく、はるかに効率的です。これはどのイベントループを指しますか?V8イベントループ?
議論されたエンティティの一部(例:libevなど)は、しばらく前から関連性を失っていたようですが、この質問にはまだ大きな可能性があると思います。
今日のように、抽象的なUNIX環境で、Nodeのコンテキストで、抽象的な例を使用して、イベント駆動型モデルの動作を説明してみましょう。
プログラムの視点:
上記のイベントメカニズムはlibuv AKAイベントループフレームワークと呼ばれます。ノードはこのライブラリを利用して、イベント駆動型プログラミングモデルを実装します。
ノードの視点:
ほとんどの機能はこの方法で提供されますが、ファイル操作の一部(非同期バージョン)は、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でのみ動作しました。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()
。setImmediate()
。setImmediate()
コールバックがここで呼び出されます。socket.on('close', ...)
。イベントループが実行されるたびに、Node.jsは非同期I / Oまたはタイマーを待機しているかどうかを確認し、非同期I / Oまたはタイマーがない場合は完全にシャットダウンします。
In the node-v0.9.0 version of libuv libev was removed
" を引用しましたが、nodejsにはそれについての説明がありませんchangelog
。github.com/nodejs/node/blob/master/CHANGELOG.md。そして、libevが削除された場合、nodejsでどのように非同期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がイベントキュー内の残りのイベントを保持することが防止されます。
JavaScriptの初心者として、私も同じ疑問を持っていました。NodeJSには2つのイベントループが含まれていますか?長い研究とV8の貢献者の1人との議論の後、私は次の概念を得ました。
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つずつ実行します。
スレッドプールを使用することで、計算の実行中にイベントループ内で他のことを実行できます。