質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Q&A

解決済

3回答

2683閲覧

Node.jsのイベントループとシングルスレッドについて

退会済みユーザー

退会済みユーザー

総合スコア0

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

0グッド

0クリップ

投稿2021/08/20 02:25

Node.jsのイベントループについて質問です.
これまで, nodeのシングルスレッドで動作する機構の認識は複数のリクエストがnodeのサーバーが
受け付けた場合, 一つひとつのリクエストを順にさばいていくという認識でおりました.

node.js

1const http = require('http'); // httpモジュールをrequire 2const PORT = 3000; // 待機するポートは3000 3let count = 0; // アクセスされた回数 4 5const server = http.createServer(); // Serverオブジェクトを作成 6server.on('request', doReq); // requestイベントにdoReqを登録 7server.listen(PORT); // 待機 8 9// requestイベント発火時時に実行する関数 10function doReq(req, res) { 11 if (req.url == '/dummy') { 12 setTimeout(function() { 13 count++; 14 res.end(`count:${count}`); // アクセスされた回数をクライアントに返す 15 }, 60000) 16 } else if (req.url == "/") { 17 count++; 18 res.end(`count:${count}`); // アクセスされた回数をクライアントに返す 19 } 20} 21

その認識とは, 上記のようなnodeのプログラムを考えた場合,

1 番目のリクエスト A アクセスしたURL => http://localhost:3000/dummy/
2 番面のリクエスト B アクセスしたURL => http://localhost:3000

とします.

まずAがリクエストをした場合
node.jsのTimerが60秒後に無名関数をコールスタックに配置するまで
Aのリクエストは待機中だと思います.A

その後即座に
Bがリクエストをした場合
Bの処理はsetTimeoutはなく直ぐにレスポンスを返す処理になっています.

ここからはこれまでのわたしの認識でしたが,
AがリクエストしたあとsetTimeoutで60秒間またされている間
Bのリクエストもそれに伴って60秒間(イベントループでは厳密には4msごとに区切っているそうなので,正確ではないらしいですが)
待たされるという認識でした.

しかし 上記プログラムを実際に起動し
ブラウザ側でAリクエスト (60秒間またされる)
ターミナルでcurl でBリクエストをしたところ
ターミナルのBリクエストは60秒またされずに即座にレスポンスが帰ってきました.

シングルスレッドと聴くと,前のAリクエストが終わらない限り次のリクエストまで処理が移らないと
思っていたのです.

上記のプログラムはなぜ

AとBともに一般的なマルチスレッドやマルチプロセスなサーバーと同じように
最初のリクエストで時間がかかっていても,次のリクエストをさばけているのでしょうか?

識者の方ご教授をお願い致します.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

そもそもJavaScriptやNode.jsは完全なシングルスレッドではありません。
「他の要素にケツを拭いて貰いながらそうあろうと振る舞っているだけ」です。

何故そういう言い回しをしているのかを含めて解説していきます。

AとBともに一般的なマルチスレッドやマルチプロセスなサーバーと同じように

最初のリクエストで時間がかかっていても,次のリクエストをさばけているのでしょうか?

Node.jsはシングルスレッドという弱点を補う為の工夫があり、
実際には並列処理ではないものの、
まるで並列処理としか考えられない程のタスクを並列で処理していきます。

疑似並列処理を実現する為の工夫としてイベントループが挙げられます。

それらの工夫によって
「複数のリクエスト」を同時並行で処理して捌いているので
質問文のコードは100人が同時にアクセスしても100人がほぼ同時に同じ結果を受け取ることでしょう。

まぁ、Node.jsはC10K問題を解決する為のサーバーマシンプログラムであろうとした事にも影響しています。
参考記事: C10K問題 - Wikipedia

当時ポピュラーだったApache2.2等はC言語で書かれている超高速な言語なのに
クライアント1万台でパフォーマンスが急激に落ち込むなんて意外ですよね
この辺はプログラミング言語の仕様やその扱い方に対する問題らしいのですが……
もう解決済だから詳しい所はわかりません。興味があれば調べてください。

なでC言語に不可能でNode.jsに可能なのかという話に関しては、
Node.jsがイベントループを実現する為に裏で採用しているLibuvがクッソ激烈に優秀だからです。
参考記事: Node.jsの設計をつらつらと概観する - Qiita


勉強が捗るようにイベントループに触れていきます。

Node.jsを含むJavaScriptは以下の性質を持った言語です。

  1. 今の処理を最優先で全て実行する(シングルスレッド)
  2. 1の最中に「イベント登録」を行い保存する事ができる
  3. 1終了後、「イベント登録」が残っていればプログラムの実行を終了せず、達成したイベントを巡回して探す
  4. 達成したイベントを1個取り出し、セットになっている関数を実行
  5. 必要があれば更にイベント登録を発行して裏側で作業させる

この3-5の繰り返しがイベントループです。
お偉いさんの机の上に大量の書類が積み重なり、それに承認していくような感じです。
つまり1度に叩ける関数は1個だけ、終わるまで次へいけません。

その作業が残像が出るほどの速度で回すから、
並列処理であるように錯覚するだけです。

具体的な例を挙げます。

js

1console.log(1); 2setTimeout( 3 () => console.log(2), 4 0 5); // 0ミリ秒後に実行なので、その場で条件を満たしている 6console.log(3); 7 81 -> 3 -> 2の順番で表示

何故「1 -> 3 -> 2」の順番で表示されるのか?
それはsetTimeoutがイベント登録を挟んでいるからです。

Node.jsにはイベント置き場というものが存在します
そのイベント置き場に対して、「達成条件」と「実行して欲しい処理を包んだ関数」の2個をセットで保存します。
これをイベント登録と呼びます。

setTimeoutはイベント登録に該当します。
「この処理が全て終わったら、イベントループ巡回のタイミングで、nミリ秒後経過している場合はセットの関数を実行せよ」
こういう命令とみなします。

なのでその場で条件が達成されていようがいまいが、「承知」と言うだけで流されてしまいます。
その後のイベントループという名の巡回フェイズで「そういや0ミリ秒後に実行して欲しいんだったよな?」という風に関数を取り出して実行しています。

だから2の表示が遅延されるわけです。
そして0ミリ秒後という約束はイベントループの周期や取り出す関数の都合上、即実行は出来ません。
ほぼ必ず数ミリ秒のズレは発生します。


とはいうものの、Node.jsは完全なシングルスレッドではありません。
裏で大量のC++モジュールやWebサーバ等を完全な同時並列で動かしています。

何かあればイベント登録という形でとにかく裏方で作業させて、
終わったらイベントループで結果を受け取り、後続の関数を叩いて続きの処理を促す。

こういう実装になっています。

「何かを待つならイベント登録して終われ」
これを合言葉にしているので重い処理はイベントで包んで飛ばしましょう。

投稿2021/08/20 03:39

miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

setTimeout(function() { count++; res.end(`count:${count}`); // アクセスされた回数をクライアントに返す }, 60000)

これは『指定したfunctionを60秒後に実行するまで待つ』という意味ではありません。

『指定したfunctionは60秒後まで実行しないで、他のことをする』という意味です。

投稿2021/08/20 03:01

ishina_yum

総合スコア509

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

シングルスレッドと聴くと,前のAリクエストが終わらない限り次のリクエストまで処理が移らないと

思っていたのです.

setTimeout非同期処理です。Node.jsのシングルスレッドは、「一度に同期的な処理を1つずつしかできない」というだけで、非同期処理は無関係です(実行するべきタイミングになった&他の同期処理が進んでいない段階で実行していきます)。

投稿2021/08/20 02:39

maisumakun

総合スコア145208

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/08/20 02:47

ご回答ありがとうごじます. >Node.jsのシングルスレッドは、「一度に同期的な処理を1つずつしかできない」 とありますが, 例えば http://localhost:3000/ に AとBの2つが同時にアクセスした場合 AリクエストとBリクエストは別スレッドになるということでしょうか?
maisumakun

2021/08/20 02:54

なりません。片方の処理が先に進んで、その処理のうち同期的な部分が済むまではもう一方は待たされます。
退会済みユーザー

退会済みユーザー

2021/08/20 03:14

ちょっと話がずれるかもしれないのですが, server.on('request', doReq); // requestイベントにdoReqを登録 上記の部分でhttpリクエストを複数受け付けた場合,それぞれのリクエストとdoReqイベントハンドラの紐付けというかそれぞれのリクエストに対してどのイベントハンドラの結果をブラウザに返してやればよいのかは serverオブジェクトがすべて内部で担っているという認識をしていればよいのですかね?
maisumakun

2021/08/20 03:28

そうですね、JavaScriptのプログラミングをする上では考える必要はないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問