ホームページ  >  記事  >  ウェブフロントエンド  >  ノードイベントループ(EventLoop)メカニズムの深い理解

ノードイベントループ(EventLoop)メカニズムの深い理解

青灯夜游
青灯夜游転載
2023-03-16 20:11:321896ブラウズ

メイン スレッドは「タスク キュー」からイベントを読み取ります。このプロセスは周期的であるため、実行メカニズム全体はイベント ループとも呼ばれます。次の記事は、Node.js のイベントループをマスターするのに役立ちます。

ノードイベントループ(EventLoop)メカニズムの深い理解

js はブラウザと node で実行できますが、イベント ループのメカニズムは同じではありません。そして大きな違いがあります。

EventLoop メカニズムの概要

Node イベント ループ メカニズムについて説明する前に、まず 2 つの問題について説明します

イベント ループのメカニズムを学ぶ必要があるのはなぜですか?

イベント ループを学習すると、開発者は JavaScript がどのように動作するかを理解できるようになります。

イベント ループ メカニズムは何を行うのでしょうか?

イベント ループ メカニズムは、非同期 API のコールバック関数が実行のためにメイン スレッドに戻るタイミングを 管理するために使用されます。

Node.js は非同期 IO モデルを使用します。同期 API はメイン スレッドで実行され、非同期 API は基盤となる C によって維持されるスレッドで実行され、非同期 API のコールバック関数もメイン スレッドで実行されます。 [関連するチュートリアルの推奨事項:

nodejs ビデオ チュートリアル プログラミング教育 ]

JavaScript アプリケーションの実行中、多くの非同期 API のコールバック関数はいつメインに戻ることができますかスレッドはどうですか?通話はどうですか?これはイベント ループ メカニズムの機能であり、非同期 API のコールバック関数が実行のためにメイン スレッドに戻るタイミングを管理します。

EventLoop の 6 つのステージ

Node のイベント ループは 6 つのステージに分かれています。

イベント ループの各ステージには、実行されるコールバック関数を格納するキューがあり、イベント ループ メカニズムはそれらを先入れ先出しで実行します。キューが null になるまでアウト方式で続行します。

これらの 6 つのステージはすべて非同期コールバック関数を格納するため、最初にメイン スレッドの同期コードを実行し、同期コードの実行後にこれらの 6 つのステージをポーリングする必要があります。

次に、これら 6 つのステージに格納される内容を詳しく見てみましょう。

Timers

Timers : コールバック関数(setInterval, setTimeout) タイマーを保存するために使用されます。

Pendingcallbacks

Pendingcallbacks: サーバー起動時にポート操作を監視するコールバック関数など、オペレーティング システムに関連するコールバック関数を実行します。サイドアプリケーション。ここで呼び出されます。

idle、prepare

idle、prepare: システムによって内部的に使用されます。 (私たちプログラマはこれについて心配する必要はありません)

Poll

Poll: 1/O 操作のコールバック関数キューを保存します。ファイルの読み取りおよび書き込み操作のコールバック関数など。

この段階では特別な注意が必要です。イベント キューにコールバック関数がある場合、それらはキューがクリアされるまで実行されます。 そうでない場合、イベント ループは、新しいコールバック関数が入るのを待つために、しばらくこの段階で

に留まります。 しかし、この

waiting

は確実ではなく、次の 2 つの条件によって決まります。

setlmmediate キューに実行するものがあるかどうか (確認してください)位相)調整機能。この場合、待ち時間は発生しません。
  • タイマー キューには実行されるコールバック関数があり、この場合は待機はありません。イベント ループはチェック フェーズに移行し、次に Closingcallbacks フェーズに移行し、最後にタイマー フェーズから次のループに移行します。
Check

Check

: setlmmediate のコールバック関数を格納します。

Closingcallbacks

Closingcallbacks

: データベース接続を閉じるためのコールバック関数など、イベントの終了に関連するコールバックを実行します。

マクロ タスクとマイクロ タスク

ブラウザの

js

と同様に、node の非同期コードも次のように分割されます。マクロタスクとマイクロタスクですが、実行順序が異なります。

Node

マクロ タスク

# のマクロ タスクとマイクロ タスクを見てみましょう # #setlnterval
  • setimeout
  • setlmmediate
  • I/O
  • マイクロタスク

##Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

  • node
  • 内、マイクロタスクとマクロの順序は何ですかタスクの実行について?

マイクロタスクとマクロタスクの実行シーケンス

nodeでは、マイクロタスクのコールバック関数がマイクロタスクキューに配置され、マクロタスクのコールバック関数がマクロタスクキューに入れられます。

マイクロタスクはマクロタスクよりも優先されます。マイクロタスクのイベント キューに実行可能なコールバック関数がある場合、イベント ループは一時停止し、現在のステージのコールバック関数を実行した後、イベント ループの次のステージに入り、すぐにマイクロタスクのイベント キューに入ってコールバックの実行を開始します。マイクロタスクキュー内のコールバック関数が実行されると、イベントループは次のセグメントに入り、コールバック関数の実行を開始します。

マイクロタスクに関しては、特に注意する必要がある点がもう 1 つあります。つまり、nextTick もマイクロタスクですが、その優先度は他のマイクロタスクよりも高くなります。マイクロタスクを実行するときは、nextlick 内のすべてのコールバック関数が実行された後でのみ実行されます。 . 他のマイクロタスクの実行が開始されます。

一般に、メイン スレッド同期コードが実行されると、最初にマイクロタスクがクリアされ (マイクロタスクがマイクロタスクを生成し続ける場合は、再度クリアされます)、次のイベント ループ ステージに進みます。また、マイクロタスクの実行は、イベント ループの 6 つのステージに分散されます。つまり、各イベント ループが次のステージに入る前に、マイクロタスク キューが空かどうかが判断され、空の場合は次のステージに進みます。それ以外の場合は、マイクロタスクが最初にクリアされます。

コードの練習を使用して、上で述べたことを確認してみましょう。

コード例

最初に同期を実行し、次に非同期で実行します

Node アプリケーションが起動した後、すぐにはイベント ループに入りませんが、最初に同期コードが上から下に実行されます。同期 API はすぐに実行され、非同期 API は実行のために C によって維持されるスレッドに渡されます。コールバック関数非同期 API のイベントは、対応するイベント キューに登録されます。すべての同期コードが実行されると、イベント ループに入ります。

console.log("start");

setTimeout(() => {
  console.log("setTimeout 1");
});

setTimeout(() => {
  console.log("setTimeout 2");
});

console.log("end");

実行結果を見てみましょう

ノードイベントループ(EventLoop)メカニズムの深い理解

最初に同期コードが実行され、次にイベント ループに入って非同期コードが実行されることがわかります。 In timers ステージは 2 つの setTimeout コールバックを実行します。

setTimeout は setImmediate の前に実行されますか?

setTimeouttimers ステージで実行されることがわかっています。 setImmediatecheck フェーズで実行されます。そして、イベント ループは timers フェーズから始まります。したがって、setTimeout が最初に実行され、次に setImmediate が実行されます。

上記の分析は正しいですか?

例を見てみましょう

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

const sleep = (delay) => {
  const startTime = +new Date();
  while (+new Date() - startTime < delay) {
    continue;
  }
};

sleep(2000);
console.log("end");

上記のコードを実行すると、出力は次のようになります

ノードイベントループ(EventLoop)メカニズムの深い理解

最初に実行しますsetTimeout次に実行します setImmediate

次に、上記のコードを変換し、遅延器を削除し、何が出力されるかを確認しましょう

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

これを 7 回実行しました。 setImmediate

ノードイベントループ(EventLoop)メカニズムの深い理解

が最初に 2 回実行されました。何が起こったのでしょうか?最初は timers ステージで、次に check ステージではないでしょうか?どうすれば変わるでしょうか?

実際には、イベント ループに入るときに非同期コールバックの準備が完全に整っているかどうかによって異なります。最初の例では、2000 ミリ秒の遅延があるため、イベント ループに入るときに setTimeout コールバックの準備ができている必要があります。したがって、実行順序は変わりません。ただし、この例では、メインスレッドには実行する同期コードがないため、最初からイベント ループに入りますが、イベント ループに入るときに、setTimeout のコールバックが必ずしも完全に準備されているわけではないため、最初に check ステージで setImmediate を実行し、次に timerssetTimeout コールバックを実行するコールバック関数があります。次のイベントループのステージ。

どのような状況で、同じ遅延時間でも setImmediate コールバック関数が setTimeout コールバックよりも優先されますか?

これは実際には非常に簡単で、timers ステージと check ステージの間の Pendingcallbacks、idle、prepare、poll にこれら 2 つを置くだけです。どのステージでも大丈夫です。これらのステージが実行された後は、必ず check ステージに進み、次に timers ステージに進むためです。

poll ステージを例として、これら 2 つを IO 操作に記述します。

const fs = require("fs");

fs.readFile("./fstest.js", "utf8", (err, data) => {
  setTimeout(() => {
    console.log("setTimeout");
  });

  setImmediate(() => {
    console.log("setImmediate");
  });
});

も 7 回実行しますが、毎回最初に setImmediate が実行されることがわかります。

ノードイベントループ(EventLoop)メカニズムの深い理解

したがって、一般に、同じ遅延時間でも、setTimeoutsetImmediate より前に 100% 実行されるわけではありません。

先微任务再宏任务

主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

console.log("end");

我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

ノードイベントループ(EventLoop)メカニズムの深い理解

nextTick优于其它微任务

在微任务中nextTick的优先级是最高的。

我们来看下面的例子

console.log("start");

setTimeout(() => {
  console.log("setTimeout");
});

setImmediate(() => {
  console.log("setImmediate");
});

Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

process.nextTick(() => {
  console.log("process.nextTick");
});

console.log("end");

我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

ノードイベントループ(EventLoop)メカニズムの深い理解

微任务穿插在各个阶段间执行

怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

// timers阶段
setTimeout(() => {
  console.log("setTimeout");

  Promise.resolve().then(() => {
    console.log("setTimeout Promise.resolve");
  });
});

// check阶段
setImmediate(() => {
  console.log("setImmediate");
  Promise.resolve().then(() => {
    console.log("setImmediate Promise.resolve");
  });
});

// 微任务
Promise.resolve().then(() => {
  console.log("Promise.resolve");
});

// 微任务
process.nextTick(() => {
  console.log("process.nextTick");
  Promise.resolve().then(() => {
    console.log("nextTick Promise.resolve");
  });
});

我们来执行上面的代码

ノードイベントループ(EventLoop)メカニズムの深い理解

可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

这也就印证了微任务会穿插在各个阶段之间运行。

ノードイベントループ(EventLoop)メカニズムの深い理解

总结

所以对于Node中的事件循环你只需要背好一以下几点就可以了

  • 当主线程同步代码执行完毕后才会进入事件循环

  • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

  • 事件循环中会先执行微任务再执行宏任务。

  • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

  • 微任务中process.nextTick的优先级最高,会优先执行。

更多node相关知识,请访问:nodejs 教程

以上がノードイベントループ(EventLoop)メカニズムの深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。