342

私はノードプログラマーではありませんが、シングルスレッドのノンブロッキングIOモデルがどのように機能するかに興味があります。記事understanding-the-node-js-event-loopを読んだ後、私はそれについて本当に混乱しています。モデルの例を示しました。

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Que:スレッドが1つしかないため、リクエストA(最初に来る)とBが2つある場合、サーバー側プログラムがリクエストAを最初に処理します。SQLクエリを実行すると、I/O待機を表すsleepステートメントが実行されます。そして、プログラムはI/O待機中にスタックし、Webページを後ろにレンダリングするコードを実行できません。プログラムは待機中にリクエストBに切り替わりますか?私の意見では、シングルスレッドモデルのため、あるリクエストを別のリクエストに切り替える方法はありません。ただし、サンプルコードのタイトルには、コードを除いてすべてが並行して実行されることが示されています。

(PSノードを使用したことがないので、コードを誤解しているかどうかはわかりません。)待機中にノードがAからBに切り替わる方法は?また、ノードのシングルスレッドノンブロッキングIOモデルを簡単に説明できますか?手伝っていただければ幸いです。:)

4

8 に答える 8

394

Node.jsは、サポートされているOS(少なくとも、Unix、OS X、およびWindows)によって提供される非同期(非ブロッキング)入出力用のapis/syscallsを抽象化するクロスプラットフォームライブラリであるlibuvに基づいて構築されています。

非同期IO

このプログラミングモデルでは、ファイルシステムによって管理されるデバイスとリソース(ソケット、ファイルシステムなど)でのオープン/読み取り/書き込み操作は、呼び出し元のスレッドをブロックせず(通常の同期cのようなモデルのように)、マークを付けるだけです。新しいデータまたはイベントが利用可能になったときに通知されるプロセス(カーネル/ OSレベルのデータ構造)。Webサーバーのようなアプリの場合、プロセスは、通知されたイベントがどのリクエスト/コンテキストに属しているかを把握し、そこからリクエストの処理を続行する責任があります。これは必然的に、OSへのリクエストを発信したスタックフレームとは異なるスタックフレームにいることを意味することに注意してください。OSは、シングルスレッドプロセスが新しいイベントを処理するためにプロセスのディスパッチャに譲らなければならなかったためです。

私が説明したモデルの問題は、それが本質的に非シーケンシャルであるため、プログラマーにとってなじみがなく、推論するのが難しいことです。「関数Aでリクエストを行い、その結果を別の関数で処理する必要があります。この場合、Aのローカルユーザーは通常利用できません。」

ノードのモデル(継続渡しスタイルとイベントループ)

Nodeは、JavaScriptの言語機能を活用して、プログラマーに特定のプログラミングスタイルを採用するように誘導することで、このモデルをもう少し同期的に見えるようにするという問題に取り組んでいます。IOを要求するすべての関数には、のような署名がfunction (... parameters ..., callback)あり、要求された操作が完了したときに呼び出されるコールバックを与える必要があります(ほとんどの時間は、OSが完了を通知するのを待つために費やされることに注意してください。他の仕事に費やした)。Javascriptのクロージャのサポートにより、コールバックの本体内の外部(呼び出し)関数で定義した変数を使用できます。これにより、ノードランタイムによって個別に呼び出されるさまざまな関数間の状態を維持できます。継続渡しスタイルも参照してください。

さらに、IO操作を生成する関数を呼び出した後、呼び出し元の関数は通常return、ノードのイベントループを制御します。このループは、実行がスケジュールされた次のコールバックまたは関数を呼び出します(おそらく、対応するイベントがOSによって通知されたため)。これにより、複数の要求の同時処理が可能になります。

ノードのイベントループは、カーネルのディスパッチャーにいくらか似ていると考えることができます。カーネルは、保留中のIOが完了すると、ブロックされたスレッドの実行をスケジュールし、ノードは、対応するイベントが発生したときにコールバックをスケジュールします。

高度な同時実行、並列処理なし

最後に、「コードを除いてすべてが並行して実行される」というフレーズは、すべてのjsを多重化してシーケンス処理することにより、ノードが1つのスレッドで数十万のオープンソケットからのリクエストを同時に処理できるようにするポイントをキャプチャするという適切な役割を果たします。単一の実行ストリームのロジック(「すべてが並列で実行される」と言っても、ここではおそらく正しくありません。並行性と並列性を参照してください。違いは何ですか?)。ほとんどの時間は実際にはネットワークまたはディスク(データベース/ソケット)の待機に費やされ、ロジックは実際にはCPUに負荷をかけないため、これはWebアプリケーションサーバーで非常にうまく機能します。つまり、これはIOバウンドワークロードでうまく機能します。

于 2013-02-10T11:41:36.507 に答える
219

さて、いくつかの視点を与えるために、node.jsをapacheと比較してみましょう。

ApacheはマルチスレッドHTTPサーバーであり、サーバーが受信するすべてのリクエストに対して、そのリクエストを処理する個別のスレッドを作成します。

一方、Node.jsはイベント駆動型であり、すべてのリクエストをシングルスレッドから非同期で処理します。

AとBがapacheで受信されると、リクエストを処理する2つのスレッドが作成されます。それぞれが個別にクエリを処理し、それぞれがページを提供する前にクエリ結果を待機します。このページは、クエリが終了するまでのみ提供されます。サーバーは結果を受信するまで残りのスレッドを実行できないため、クエリフェッチはブロックされています。

ノードでは、c.queryは非同期で処理されます。つまり、c.queryはAの結果をフェッチしている間、ジャンプしてBのc.queryを処理し、Aの結果が到着すると、結果をコールバックに送り返します。応答。Node.jsは、フェッチが終了したときにコールバックを実行することを認識しています。

私の意見では、これはシングルスレッドモデルであるため、あるリクエストから別のリクエストに切り替える方法はありません。

実際、ノードサーバーは常にそれを実行します。スイッチを作成するために(非同期動作)、使用するほとんどの関数にはコールバックがあります。

編集

SQLクエリはmysqlライブラリから取得されます。SQLリクエストをキューに入れるためのコールバックスタイルとイベントエミッターを実装します。それらを非同期的に実行することはありません。これは、非ブロッキングI/Oの抽象化を提供する内部libuvスレッドによって実行されます。クエリを実行するには、次の手順を実行します。

  1. dbへの接続を開きます。接続自体は非同期で行うことができます。
  2. dbが接続されると、クエリがサーバーに渡されます。クエリはキューに入れることができます。
  3. メインイベントループは、コールバックまたはイベントで完了の通知を受け取ります。
  4. メインループはコールバック/イベントハンドラーを実行します。

httpサーバーへの着信要求も同様の方法で処理されます。内部スレッドアーキテクチャは次のようなものです。

node.jsイベントループ

C ++スレッドは、非同期I / O(ディスクまたはネットワーク)を実行するlibuvスレッドです。メインイベントループは、リクエストをスレッドプールにディスパッチした後も実行を続けます。待機もスリープもしないため、より多くの要求を受け入れることができます。SQLクエリ/HTTPリクエスト/ファイルシステムの読み取りはすべてこの方法で行われます。

于 2013-02-10T11:09:51.653 に答える
58

Node.jsは舞台裏でlibuvを使用します。libuvにはスレッドプールがあります(デフォルトではサイズ4)。したがって、Node.jsはスレッドを使用して同時実行を実現します。

ただしコードは単一のスレッドで実行されます(つまり、Node.js関数のすべてのコールバックは、同じスレッド、いわゆるループスレッドまたはイベントループで呼び出されます)。人々が「Node.jsは単一のスレッドで実行される」と言うとき、彼らは実際には「Node.jsのコールバックは単一のスレッドで実行される」と言っています。

于 2016-11-28T19:56:16.630 に答える
9

Node.jsは、イベントループプログラミングモデルに基づいています。イベントループはシングルスレッドで実行され、イベントを繰り返し待機してから、それらのイベントにサブスクライブされているイベントハンドラーを実行します。イベントは、例えばすることができます

  • タイマー待機が完了しました
  • データの次のチャンクをこのファイルに書き込む準備ができています
  • 新たなHTTPリクエストが届きます

これらはすべてシングルスレッドで実行され、JavaScriptコードが並行して実行されることはありません。これらのイベントハンドラーが小さく、さらに多くのイベント自体を待つ限り、すべてがうまく機能します。これにより、単一のNode.jsプロセスで複数のリクエストを同時に処理できます。

(イベントが発生する場所として、内部には少し魔法があります。その一部には、並行して実行される低レベルのワーカースレッドが含まれます。)

このSQLの場合、データベースクエリを実行してからコールバックで結果を取得するまでの間に多くのことが発生します(イベント)。その間、イベントループはアプリケーションに生命を吹き込み続け、一度に1つの小さなイベントで他のリクエストを進めます。したがって、複数のリクエストが同時に処理されます。

イベントループのハイレベルビュー

によると:「10,000ftからのイベントループ-Node.jsの背後にあるコアコンセプト」

于 2015-09-14T07:51:52.653 に答える
6

関数c.query()には2つの引数があります

c.query("Fetch Data", "Post-Processing of Data")

この場合の「データのフェッチ」操作はDB-Queryですが、これはNode.jsによって、ワーカースレッドを生成し、DB-Queryを実行するこのタスクを与えることで処理できます。(Node.jsは内部でスレッドを作成できることを忘れないでください)。これにより、関数は遅延なく瞬時に戻ることができます

2番目の引数「データの後処理」はコールバック関数です。ノードフレームワークはこのコールバックを登録し、イベントループによって呼び出されます。

したがって、ステートメントc.query (paramenter1, parameter2)は即座に返され、ノードが別の要求に対応できるようになります。

PS:ノードを理解し始めたばかりです。実際、これを@Philipへのコメントとして書きたかったのです が、十分なレピュテーションポイントがなかったため、回答として書きました。

于 2014-06-09T09:43:22.810 に答える
3

もう少し読んでみると-「もちろん、バックエンドには、DBアクセスとプロセス実行のためのスレッドとプロセスがあります。ただし、これらはコードに明示的に公開されていないため、知っている以外に心配することはできません。これらのスレッドからの結果はイベントループを介してコードに返されるため、たとえばデータベースや他のプロセスとのI / Oインタラクションは、各リクエストの観点から非同期になります。」

about-「コードを除いてすべてが並行して実行されます」-コードは同期的に実行され、IOの待機などの非同期操作を呼び出すたびに、イベントループがすべてを処理してコールバックを呼び出します。それはあなたが考えなければならないことではありません。

あなたの例では、2つのリクエストA(最初に来る)とBがあります。リクエストAを実行すると、コードは引き続き同期的に実行され、リクエストBを実行します。イベントループはリクエストAを処理し、終了するとリクエストAのコールバックを呼び出します。結果、同じことがリクエストBにも当てはまります。

于 2013-02-10T10:25:19.013 に答える
1

さて、これまでのところほとんどのことが明確になっているはずです...トリッキーな部分はSQLです:それが実際には別のスレッドまたはプロセス全体で実行されていない場合、SQL実行は個々のステップに分割する必要があります(非同期実行用に作成されたSQLプロセッサ!)、非ブロッキングのものが実行され、ブロッキングのもの(スリープなど)が実際にカーネルに転送され(アラーム割り込み/イベントとして)、イベントリストに追加されます。メインループ。

つまり、たとえばSQLなどの解釈はすぐに実行されますが、待機中(kqueue、epoll、...構造体のカーネルによって将来発生するイベントとして格納されます。他のIO操作と一緒に) )メインループは他のことを実行し、最終的にそれらのIOで何かが発生したかどうかをチェックして待機します。

つまり、言い換えると、プログラムがスタックすることはなく(取得が許可されることもありません)、スリープ状態の呼び出しが実行されることはありません。それらの義務は、カーネル(何かを書く、何かがネットワークを介して来るのを待つ、時間が経過するのを待つ)または別のスレッドまたはプロセスによって行われます。–ノードプロセスは、各イベントループサイクルで1回、OSへの唯一のブロッキング呼び出しでカーネルによってこれらの義務の少なくとも1つが終了したかどうかをチェックします。すべての非ブロッキングが実行されると、そのポイントに到達します。

クリア?:-)

ノードがわかりません。しかし、c.queryはどこから来たのでしょうか?

于 2013-11-09T10:50:18.497 に答える
0

これevent loopにより、Node.jsは、JavaScriptがシングルスレッドであるにもかかわらず、可能な限りシステムカーネルに操作をオフロードすることで、非ブロッキングI/O操作を実行できます。event loopマネージャーと考えてください。

  • 新しいリクエストはキューに送信され、によって監視されますsynchronous event demultiplexer。ご覧のとおり、各操作ハンドラーも登録されています。

ここに画像の説明を入力してください

  • 次に、これらの要求は同期的にスレッドプール(ワーカープール)に送信されて実行されます。JavaScriptは非同期I/O操作を実行できません。ブラウザ環境では、ブラウザが非同期操作を処理します。ノード環境では、非同期操作はlibuvを使用してによって処理されC++ます。スレッドのプールのデフォルトサイズは4ですが、起動時にUV_THREADPOOL_SIZE環境変数を任意の値(最大128)に設定することで変更できます。スレッドプールサイズ4は、一度に4つのリクエストを実行できることを意味します。イベントデマルチプレクサに5つのリクエストがある場合、4つがスレッドプールに渡され、5つ目が待機します。各リクエストが実行されると、結果は`イベントデマルチプレクサに返されます。

ここに画像の説明を入力してください

  • 一連のI/O操作が完了すると、イベントデマルチプレクサは対応する一連のイベントをイベントキューにプッシュします。

ここに画像の説明を入力してください

ハンドラーはコールバックです。これで、イベントループはイベントキューを監視します。準備ができているものがある場合は、スタックにプッシュされてコールバックが実行されます。最終的にコールバックはスタックで実行されることを忘れないでください。一部のコールバックには他のコールバックに優先順位があり、イベントループはそれらの優先順位に基づいてコールバックを選択することに注意してください。

于 2022-01-27T03:55:50.503 に答える