1. ホーム
  2. javascript

[解決済み] イベントループを理解する

2022-06-24 16:14:25

質問

考えていて、こんなことを思いつきました。

このコードを下に見てみましょう。

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

リクエストが来ると、JSエンジンは上のコードを順次実行し始めます。最初の2つの呼び出しは同期呼び出しです。しかし setTimeout メソッドになると、非同期実行になる。しかし、JSはそこからすぐに戻り、実行を継続する、これは Non-Blocking または Async . そして、それは他の等でも動作し続けます。

この実行結果は次のようになります。

a c d b

つまり、基本的には2番目の setTimeout は先に終了し、そのコールバック関数は最初のものよりも早く実行されます。

ここではシングルスレッド・アプリケーションについて話しています。JSエンジンはこれを実行し続け、最初のリクエストを完了しない限り、2番目のリクエストに行きません。しかし、良いことに、JSエンジンは setTimeout のようなブロック操作を待たずに、新しいリクエストを受け入れるので、より速くなります。

しかし、私の疑問は以下の項目に関して生じます。

#1: シングルスレッド・アプリケーションの話であれば、どのようなメカニズムでプロセス setTimeouts を処理する一方で、JSエンジンはさらに多くのリクエストを受け付け、それらを実行するのでしょうか?シングルスレッドはどのようにして他のリクエストに対応し続けるのでしょうか?何が setTimeout に対して動作し、他のリクエストが入ってきて実行され続けるのでしょうか?

#2: もし、これらの setTimeout 関数が裏で実行され、さらにリクエストが来て実行されるのであれば、何が裏で非同期実行を行うのでしょうか?私たちが話している、この EventLoop ?

#3: しかし、メソッド全体を EventLoop に置くべきで、そうすれば全体が実行され、コールバックメソッドが呼ばれるのでは?これは、コールバック関数について話しているときに私が理解していることです。

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

しかしこの場合、JSエンジンはどのようにして非同期関数であることを知り、コールバックを EventLoop ? おそらく async キーワードのようなもの、またはJS Engineが引き受けるメソッドが非同期メソッドであり、それに応じて処理されるべきであることを示す属性のようなものでしょう。

#4: しかし 記事 には、私が推測していたのとはまったく逆のことが書かれています。

イベントループはコールバック関数の待ち行列です。非同期関数が実行されると、コールバック関数がキューに押し込まれます。 関数が実行されると、そのコールバック関数がキューにプッシュされます。このとき が実行されるまで、JavaScript エンジンはイベントループの処理を開始しません。 が実行されるまで、JavaScript エンジンはイベントループの処理を開始しません。

#5: そして、参考になりそうなこの画像がありますが、画像の最初の説明は、質問番号4で述べたことと全く同じことを言っています。

そこで私がここで質問したいのは、上記の項目についての説明を受けることです。

どのように解決するのですか?

<ブロッククオート

1: シングルスレッド・アプリケーションの場合、JSエンジンが多くのリクエストを受け付け、それを実行している間、setTimeoutはどのように処理されるのでしょうか?そのシングルスレッドは他のリクエストに対応し続けるのではないでしょうか? 他のリクエストが来て実行され続ける間、誰がsetTimeoutに取り組み続けるのでしょう。

あなたのプログラムのJavaScriptを実際に実行するのは、nodeのプロセスの中で1つのスレッドだけです。しかし、node自体の中では、実際にはイベントループ機構の操作を処理する複数のスレッドがあり、これにはIOスレッドのプールと他の一握りのスレッドが含まれます。重要なのは、これらのスレッドの数が、スレッドパーコネクション並行性モデルのように処理される同時接続の数に対応しないことです。

さて、"setTimeouts" を実行することについてですが、あなたが setTimeout を呼び出したとき、node が行うことは基本的に、将来のある時点で実行される関数のデータ構造を更新することです。基本的に、node は実行が必要なもののキューの束を持っており、イベント ループの "tick" ごとに 1 つを選択し、キューから削除して、それを実行するのです。

理解すべき重要なことは、node は重い作業のほとんどを OS に依存しているということです。そのため、入ってくるネットワーク リクエストは実際には OS 自身によって追跡され、ノードがそれを処理する準備ができたら、システム コールを使用して OS に処理可能なデータを含むネットワーク リクエストを要求するだけです。つまり、ノードが行うIOの多くは、「Hey OS, got a network connection with data ready to read?」または「Hey OS, any of my outstanding filesystem calls have data ready?」のどちらかです。nodeは内部アルゴリズムとイベントループエンジンの設計に基づき、実行するJavaScriptを1つ選び、実行し、また同じ処理を繰り返すことになります。これがイベントループの意味するところです。Nodeは基本的に常に「次に実行すべき小さなJavaScriptは何か」を判断して実行しています。これにはOSが完了したIOや、JavaScriptの中で setTimeout または process.nextTick .

2: もしこれらのsetTimeoutが、より多くのリクエストが来て実行されている間に裏で実行されるなら、裏で非同期実行を行うものは、我々がEventLoopについて話しているものでしょうか?

裏側で実行されるJavaScriptはありません。プログラム内のすべてのJavaScriptは、フロントとセンターで、一度に1つずつ実行されます。裏で起こっていることは、OSがIOを処理し、ノードがその準備を待ち、ノードが実行を待つJavaScriptのキューを管理することです。

3: JS Engineはどのようにして非同期関数であることを知り、EventLoopに入れることができるのでしょうか。

ノードコアには、システムコールを行うため非同期である関数の固定セットがあり、ノードはOSまたはC++を呼び出さなければならないため、これらがどれであるかを知っています。基本的にすべてのネットワークとファイルシステムのIO、および子プロセスのインタラクションは非同期で、JavaScriptがnodeに非同期に何かを実行させる唯一の方法は、nodeコアライブラリが提供する非同期関数のいずれかを呼び出すことで実現できます。独自の API を定義する npm パッケージを使用している場合でも、イベント ループを生成するために、最終的にその npm パッケージのコードは node core の非同期関数の 1 つを呼び出すことになり、そのときに node は tick が完了しイベント ループ アルゴリズムを再び開始できることを認識します。

4 イベントループは、コールバック関数のキューです。非同期関数が実行されると、コールバック関数がキューにプッシュされます。JavaScriptエンジンは、非同期関数が実行された後のコードまで、イベントループの処理を開始しません。

はい、これは本当ですが、誤解を招きかねません。重要なのは、通常のパターンが

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

つまり、フィボナッチ数をメモリ上で同期的にすべて同じ刻みで数えることによって、イベントループを完全にブロックすることができるのです。これは協調的並行処理です。JavaScript の各ティックは、ある程度の合理的な時間内にイベント ループを終了しなければならず、そうでなければアーキテクチャ全体が失敗します。