ホームページ  >  記事  >  ウェブフロントエンド  >  Node.jsのイベントループ(Event Loop)とスレッドpool_node.jsの詳細説明

Node.jsのイベントループ(Event Loop)とスレッドpool_node.jsの詳細説明

WBOY
WBOYオリジナル
2016-05-16 16:17:231197ブラウズ

ノードの「イベント ループ」は、大規模な同時実行と高スループットを処理する機能の中核です。これは最も魔法の部分であり、これによれば、Node.js は基本的に「シングルスレッド」として理解され、同時にバックグラウンドでの任意の操作の処理も可能になります。この記事では、イベント ループがどのように機能するかを説明し、その魅力を感じていただけるようにします。

イベント駆動型プログラミング

イベント ループを理解するには、まずイベント駆動型プログラミングを理解する必要があります。 1960年に登場しました。現在、UI プログラミングではイベント駆動型プログラミングが頻繁に使用されています。 JavaScript の主な用途の 1 つは DOM と対話することなので、イベントベースの API を使用するのは自然なことです。

簡単に定義すると、イベント駆動型プログラミングは、イベントまたは状態の変化を通じてアプリケーションのフローを制御します。一般にイベント監視を通じて実装され、イベントが検出されると (つまり、状態が変化すると)、対応するコールバック関数が呼び出されます。おなじみですね?実際、これは Node.js イベント ループの基本的な動作原理です。

クライアント側の JavaScript 開発に精通している場合は、DOM 要素と組み合わせてユーザー インタラクションを提供するために使用される、element.onclick() などの .on*() メソッドについて考えてみましょう。この動作モードでは、単一のインスタンスで複数のイベントをトリガーできます。 Node.js は、サーバー側のソケットや「http」モジュールなどの EventEmitters (イベント ジェネレーター) を通じてこのパターンをトリガーします。 1 つ以上の状態変更を単一のインスタンスからトリガーできます。

もう 1 つの一般的なパターンは、成功と失敗を表現することです。一般に、2 つの一般的な実装方法があります。 1 つ目は、「エラー例外」を通常はコールバック関数の最初のパラメータとしてコールバックに渡すことです。 2 つ目は Promises デザイン パターンを使用し、ES6 を追加しています。注* Promise モードは、次のようなコールバック関数の深いネストを避けるために、jQuery と同様の関数チェーン記述方法を使用します。

コードをコピー コードは次のとおりです:
$.getJSON('/getUser').done(successHandler).fail(failHandler)

「fs」(ファイルシステム) モジュールは、ほとんどの場合、例外をコールバックに渡すスタイルを採用しています。技術的には fs.readFile() 添付イベントなどの特定の呼び出しをトリガーしますが、API は単にユーザーに警告し、操作の成功または失敗を表現するだけです。このような API の選択は、技術的な制限ではなく、アーキテクチャ上の考慮事項に基づいて行われます。

よくある誤解は、イベント エミッターもイベントの起動時に本質的に非同期であるということですが、これは正しくありません。以下は、これを示す簡単なコード スニペットです。


コードをコピー コードは次のとおりです:
function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);
MyEmitter.prototype.doStuff = function doStuff() {

console.log('before')
エミッター.emit('火')
console.log('after')}
};

var me = new MyEmitter();

me.on('火', function() {
console.log('emit fired');
});

me.doStuff();

// 出力:
// 前
// 発砲される
// 後

注* Emitter.emit が非同期の場合、出力は

である必要があります // 前
// 後
// 発砲される


EventEmitter は、非同期で完了する必要がある操作を通知するために使用されることが多いため、非同期的に動作することがよくありますが、EventEmitter API 自体は完全に同期しています。リスニング関数は内部的に非同期で実行できますが、すべてのリスニング関数は追加された順序で同期的に実行されることに注意してください。

メカニズムの概要とスレッドプール

ノード自体は複数のライブラリに依存しています。その 1 つは、非同期イベント キューと実行を処理するための素晴らしいライブラリである libuv です。

ノードは、既存の機能を実装するためにオペレーティング システム カーネルを可能な限り利用します。応答リクエストの生成、接続の転送、処理のためのシステムへの委託など。たとえば、着信接続は、Node で処理できるようになるまで、オペレーティング システムを通じてキューに入れられます。

Node にはスレッド プールがあると聞いたことがあるかもしれません。「Node がタスクを順番に処理するのに、なぜスレッド プールが必要なのでしょうか? これは、カーネル内ですべてのタスクが処理されるわけではないためです。」命令は非同期で実行されます。この場合、Node.JS は、ブロックされることなくイベント ループの実行を継続できるように、動作中にスレッドを一定期間ロックできる必要があります。

以下は、内部動作メカニズムを示す簡単な図の例です:


┌─────────────┐
╭─►│ タイマー タイマー │ └───────┬───────┘
│ ┌───────┴───────┐
call係中のコールバック │└。—七面 | │ │ │ POLL ││── 接続、 │
│以来 │┌。—七面 ╰─── ┤ setImmediate ━━━━━━━━━━┘

イベント ループの内部動作については、理解するのが難しい点がいくつかあります。

すべてのコールバックは、イベント ループ (タイマーなど) の 1 つのステージの終了時と次のステージに移行する前に、process.nextTick() を介してプリセットされます。これにより、無限ループを引き起こす process.nextTick() への再帰呼び出しの可能性が回避されます。
「保留中のコールバック」とは、他のイベント ループ サイクルによって処理されないコールバック キュー内のコールバック (たとえば、fs.write に渡される) です。

イベント エミッターとイベント ループ

EventEmitter を作成することで、イベント ループとの対話を簡素化します。これは、イベントベースの API をより簡単に作成できるようにする汎用ラッパーです。この 2 つがどのように相互作用するかは、開発者を混乱させることがよくあります。

次の例は、イベントが同期的にトリガーされることを忘れると、イベントが見逃される可能性があることを示しています。

コードをコピー コードは次のとおりです:

// v0.10 以降、require('events').EventEmitter は必要なくなりました
var EventEmitter = require('events');
var util = require('util');

関数 MyThing() {
EventEmitter.call(this);

doFirstThing();
this.emit('thing1');
}
util.inherits(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// 申し訳ありませんが、このイベントは決して発生しません
});


MyThing() はイベントをリッスンする前にインスタンス化する必要があるため、上記の「thing1」イベントは MyThing() によってキャプチャされることはありません。追加のクロージャを追加しない簡単な回避策は次のとおりです:
コードをコピー コードは次のとおりです:

var EventEmitter = require('events');
var util = require('util');

関数 MyThing() {
EventEmitter.call(this);

doFirstThing();
setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);

関数 EmitThing1(self) {
self.emit('thing1');
}

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// 実行されました
});

次のソリューションも機能しますが、パフォーマンスが多少犠牲になります:

コードをコピー コードは次のとおりです:

function MyThing() {
EventEmitter.call(this);

doFirstThing();
// Function#bind() を使用するとパフォーマンスが低下します
setImmediate(this.emit.bind(this, 'thing1'));
}
util.inherits(MyThing, EventEmitter);


もう 1 つの問題は、エラーのトリガーです。アプリケーション内の問題を見つけるのは十分に困難ですが、コール スタック (*e.stack に注意) がなければ、デバッグはほぼ不可能です。リモートの非同期リクエストによって Error が受信されると、コール スタックが失われます。考えられる解決策は 2 つあります。同期的にトリガーするか、エラーが他の重要な情報と一緒に渡されるようにするかです。以下の例は両方の解決策を示しています:
コードをコピー コードは次のとおりです:

MyThing.prototype.foo = function foo() {
// このエラーは非同期的にトリガーされます
var er = doFirstThing();
if (er) {
//トリガーされると、オンサイトのコールスタック情報を保持するために新しいエラーを作成する必要があります
setImmediate(emitError, this, new Error('Bad things'));
戻る;
}

// エラーをトリガーし、即座に (同期的に) 処理します
var er = doSecondThing();
if (er) {
This.emit('エラー', 'さらに悪いこと');
戻る;
}
}


状況を評価します。エラーが発生した場合、即座に対処できます。または、簡単に処理できる、または後で処理できる些細な例外である可能性があります。さらに、構築されたオブジェクト インスタンスが不完全である可能性が高いため、コンストラクターを介して Error を渡すことはお勧めできません。先ほど直接 Error がスローされた場合は例外です。

結論

この記事では、イベント ループの内部動作と技術的な詳細について簡単に説明します。それはすべてよく考えられています。別の記事では、イベント ループとシステム カーネルの相互作用について説明し、NodeJS の非同期操作の魅力を示します。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。