Nodeのイベントループについて話しましょう

青灯夜游
リリース: 2023-04-11 19:08:21
転載
1581 人が閲覧しました

イベント ループは Node.js の基本的な部分です。メイン スレッドがブロックされないようにすることで、非同期プログラミングが可能になります。イベント ループを理解することは、効率的なアプリケーションを構築するために重要です。次の記事では、Node のイベント ループについて詳しく説明します。お役に立てれば幸いです。

Nodeのイベントループについて話しましょう

あなたはしばらく Node.js を使用し、いくつかのアプリを構築し、さまざまなモジュールを試し、非同期プログラミングにも慣れてきたと思います。しかし、何かがあなたを悩ませ続けます - イベントループです。

あなたも私と同じなら、イベント ループを理解するためにドキュメントを読んだり、ビデオを見たりするのに数え切れないほどの時間を費やしてきました。しかし、経験豊富な開発者であっても、その仕組みを完全に理解するのは難しいかもしれません。そのため、Node.js イベント ループを完全に理解できるように、このビジュアル ガイドを用意しました。それでは、座ってコーヒーを飲みながら、Node.js イベント ループの世界に飛び込んでみましょう。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル プログラミング教育 ]

JavaScript での非同期プログラミング

から始めますJavaScript での非同期プログラミングのレビューが始まります。 JavaScript は Web、モバイル、デスクトップ アプリケーションで使用されますが、本質的には、JavaScript は同期、ブロッキング、シングルスレッド言語 であることを覚えておくことが重要です。短いコードスニペットを通してこの文を理解してみましょう。

// index.js

function A() {
  console.log("A");
}

function B() {
  console.log("B");
}

A()
B()

// Logs A and then B
ログイン後にコピー

JavaScript は同期です

メッセージをコンソールに記録する 2 つの関数がある場合、コードは上から下に実行され、毎回 1 つだけ実行されます。ライン。上記のコード スニペットでは、A が B の前に記録されていることがわかります。

JavaScript がブロックしています

JavaScript は同期の性質によりブロックされています。前のプロセスにどれだけ時間がかかっても、前のプロセスが完了するまで後続のプロセスは開始されません。コード スニペットでは、関数 A が大きなコード ブロックを実行する必要がある場合、JavaScript は関数 B に分岐せずにその操作を完了する必要があります。たとえこのコードに 10 秒、あるいは 1 分かかっていたとしてもです。

お使いのブラウザでこのような状況に遭遇したことがあるかもしれません。 Web アプリケーションがブラウザーで実行され、ブラウザーに制御を返さずにコードの集中的なブロックを実行すると、ブラウザーがフリーズすることがあります。これをブロッキングと呼びます。 Web アプリケーションがプロセッサ制御をブラウザに戻すまで、ブラウザはユーザー入力の処理や他のタスクの実行を継続できなくなります。

JavaScript はシングルスレッドです

スレッドとは、JavaScript プログラムがタスクを実行するために使用できるプロセスです。各スレッドは一度に 1 つのタスクのみを実行できます。マルチスレッドをサポートし、複数のタスクを同時に実行できる他の言語とは異なり、JavaScript にはコードを実行するメインスレッドと呼ばれるスレッドが 1 つだけあります。

JavaScript の待機

ご想像のとおり、この JavaScript モデルでは、コードの実行を続行する前にデータがフェッチされるまで待たなければならないため、問題が発生します。この待機には数秒かかる場合があり、その間は他のコードを実行できません。 JavaScript が待機せずに処理を続行すると、エラーが発生します。 JavaScript で非同期動作を実装する必要があります。 Node.js にアクセスして見てみましょう。

Node.js ランタイム

Nodeのイベントループについて話しましょう

Node.js ランタイムはブラウザを使わずに利用できる環境です 使用して実行しますJavaScript プログラム。コア - ノード ランタイムは、3 つの主要コンポーネントで構成されます。

  • 外部依存関係 (V8、libuv、crypto など) は Node.js の必須機能です。
  • C 機能は、ファイル システム アクセスやネットワークなどの機能を提供します。
  • JavaScript ライブラリは、JavaScript コードを使用した C 機能の呼び出しを容易にする関数とツールを提供します。

すべての部分が重要ですが、Node.js の非同期プログラミングの主要なコンポーネントは libuv です。

Libuv

Libuv は、C 言語で書かれたクロスプラットフォームのオープンソース ライブラリです。 Node.js ランタイムにおけるその役割は、非同期操作の処理のサポートを提供することです。仕組みを見てみましょう。

Node.js ランタイムでのコードの実行

Nodeのイベントループについて話しましょう

让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。

每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。

每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。

同步代码执行

首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。

// index.js
console.log("First");
console.log("Second");
console.log("Third");
ログイン後にコピー

以下是 Node 运行时执行同步代码的可视化展示。

Nodeのイベントループについて話しましょう

执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。

执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。

最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。

异步代码执行

接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile() 作为回调函数。

Nodeのイベントループについて話しましょう

执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 1 行,在第 1 毫秒时,“First”被记录在控制台中,并弹出该函数。然后执行移动到第 2 行,在第 2毫秒时,readFile 方法被推入堆栈。由于 readFile 是异步操作,因此它会转移(off-loaded)到 libuv。

JavaScript 从调用堆栈中弹出了 readFile 方法,因为就第 2 行的执行而言,它的工作已经完成了。在后台,libuv 开始在单独的线程上读取文件内容。在第 3 毫秒时,JavaScript 继续进行到第 5 行,将 log 函数推入堆栈,“Third”被记录到控制台中,并将该函数弹出堆栈。

大约在第 4 毫秒左右,假设文件读取任务已经完成,则相关回调函数现在会在调用栈上执行, 在回调函数内部遇到 log 函数。

log 函数推入到到调用栈,“Second”被记录到控制台并弹出 log 函数 。由于回调函数中没有更多要执行的语句,因此也被弹出 。没有更多代码可运行了 ,所以全局函数也从堆栈中删除 。

控制台输出“First”,“Third”,然后是“Second”。

Libuv 和异步操作

很明显,libuv 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。

  • 当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
  • Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
  • setTimeoutsetInterval 这类延迟执行回调函数的方法又是何时执行回调函数呢?
  • 如果 setTimeoutreadFile 这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?

所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。

イベントループとは何ですか?

技術的に言えば、イベント ループは単なる C 言語プログラムです。しかし、Node.js では、同期コードと非同期コードの実行を調整するための設計パターンと考えることができます。

イベント ループの視覚化

イベント ループは、Node.js アプリケーションが実行されている限り実行されるループです。各ループには 6 つの異なるキューがあり、各キューには最終的にコール スタックで実行する必要がある 1 つ以上のコールバック関数が含まれています。

Nodeのイベントループについて話しましょう

  • まず、タイマー キュー (タイマー キュー。専門的には min-heap と呼ばれます) があり、setTimeout## と同じ番号が保存されます # コールバックsetInterval に関連する関数。
  • 2 番目に、I/O キュー (I/O キュー) があります。これには、
  • fs モジュールや http モジュールなど、すべての非同期メソッドに関連するコールバック関数が含まれています。関連するメソッドが提供されます。
  • 3 番目のキューは、ノード固有の関数である
  • setImmediate 関数に関連するコールバック関数を保持するチェック キューです。
  • 4 番目はクローズ キュー (クローズ キュー) で、非同期タスクの終了イベントに関連付けられたコールバック関数を保存します。
最後に、マイクロタスク キューを形成する 2 つの異なるキューがあります。

    nextTick キューには、
  • process.nextTick 関数に関連付けられたコールバック関数が格納されます。
  • Promise キューには、JavaScript のローカル
  • Promise に関連付けられたコールバック関数が格納されます。
タイマー、I/O、チェックおよびクローズキューはすべて libuv に属していることに注意してください。ただし、2 つのマイクロタスク キューは libuv に属しません。それにもかかわらず、これらは依然として Node ランタイム環境で重要な役割を果たし、コールバックが実行される順序において重要な役割を果たします。そうは言っても、イベント ループがどのように機能するかを理解しましょう。

イベント ループはどのように機能しますか?

写真の矢印はヒントですが、わかりにくいかもしれません。キューの優先順位について説明します。まず知っておくべきことは、ユーザーが作成したすべての同期 JavaScript コードは非同期コードよりも優先されるということです。これは、イベント ループは呼び出しスタックが空の場合にのみ機能することを意味します。

イベント ループでは、実行順序は特定のルールに従います。習得する必要のあるルールがまだいくつかありますので、1 つずつ見てみましょう:

    マイクロタスク キュー内のすべてのコールバック関数を実行します。最初に nextTick キュー内のタスク、次に Promise キュー内のタスクです。
  1. タイマー キュー内のすべてのコールバック関数を実行します。
  2. マイクロタスク キューにコールバック関数がある場合、マイクロタスク キュー内のすべてのコールバック関数
  3. は、各コールバック関数が タイマー キューで実行された後に実行されます。最初に nextTick キュー内のタスク、次に Promise キュー内のタスクです。
  4. I/O キュー内のすべてのコールバック関数を実行します。
  5. マイクロタスク キューにコールバック関数がある場合、マイクロタスク キュー内のすべてのコールバック関数は、nextTick キュー、Promise キューの順に順番に実行されます。
  6. チェック キュー内のすべてのコールバック関数を実行します。
  7. マイクロタスク キューにコールバック関数がある場合、マイクロタスク キュー内のすべてのコールバック関数
  8. は、キュー内の各コールバックを チェックした後に実行されます。最初に nextTick キュー内のタスク、次に Promise キュー内のタスクです。
  9. クローズキュー内のすべてのコールバック関数を実行します。
  10. 同じループの最後で、マイクロタスク キューを再度実行します。最初に nextTick キュー内のタスク、次に Promise キュー内のタスクです。
この時点で、さらに処理すべきコールバックがある場合は、イベント ループが再度実行されます (注釈: イベント ループはプログラムの実行中も実行され続けますが、現在処理するタスクはありません。次に、待機状態になり、新しいタスクが存在するとすぐに実行されます)、同じ手順を繰り返します。一方、すべてのコールバックが実行され、処理するコードがなくなった場合、イベント ループは終了します。

これは、Node.js で非同期コードを実行するために libuv イベント ループが行うことです。これらのルールを理解した上で、以前に尋ねた質問をもう一度見てみましょう。


非同期タスクが libuv で完了すると、Node はいつコール スタックで関連するコールバック関数を実行しますか?

回答:

コールバック関数は、コール スタックが空の場合にのみ実行されます。

ノードはコールバック関数を実行する前にコールスタックが空になるのを待ちますか?それとも通常の実行フローを中断してコールバック関数を実行しますか?

回答:

コールバック関数を実行しても、通常の実行フローは中断されません。

コールバック関数の実行を遅延させる setTimeoutsetInterval などのメソッドは、いつコールバック関数を実行しますか?

回答: setTimeout および setInterval のすべてのコールバック関数の中で最初の優先順位が実行されます (マイクロタスク キューに関係なく)。

2 つの非同期タスク (setTimeoutreadFile など) が同時に完了した場合、ノードはどのコールバック関数を実行するかをどのように決定しますかコールスタックの最初??一方が他方よりも優先されるでしょうか?

回答: 同時完了の場合、タイマー コールバックは I/O コールバックの前に実行されます。


これまで多くのことを学習してきましたが、下の図に示されている実行シーケンスを念頭に置いていただければ幸いです。これは、Node.js が舞台裏で何を行っているかを完全に示しているためです。非同期コードを実行する方法。

Nodeのイベントループについて話しましょう

しかし、「このビジュアライゼーションを検証するコードはどこにあるの?」と疑問に思うかもしれません。イベント ループ内の各キューには実装上のニュアンスがあるため、それらについて 1 つずつ説明するほうがよいでしょう。この記事は、Node.js イベント ループに関するシリーズの最初の記事です。各キューの操作の詳細については、記事の最後にあるリンクを必ず確認してください。今は心に深い印象が残っている場合でも、特定のシナリオに到達すると、いくつかの罠に陥る可能性があります。 。

結論

このビジュアル ガイドでは、JavaScript の非同期プログラミング、Node.js ランタイム、および非同期操作の処理を担当する libuv の基本について説明します。この知識があれば、Node.js の非同期の性質を利用するコードを作成するときに役立つ強力なイベント ループ モデルを構築できます。

ノード関連の知識の詳細については、nodejs チュートリアル を参照してください。

以上がNodeのイベントループについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート