1. ホーム
  2. node.js

[解決済み] スレッドプールはどのような場合に使用するのですか?

2022-09-30 10:37:58

質問

Node.jsがどのように動作するか理解しています。イベントを受信し、それをワーカープールに委譲する単一のリスナースレッドを持っています。ワーカスレッドは、それが仕事を完了するとリスナーに通知し、リスナーは呼び出し元への応答を返します。

私の質問はこれです:私がNode.jsでHTTPサーバーを立ち上げ、私のルーティングされたパスイベントの1つ(たとえば"/test/sleep")でsleepを呼び出すと、システム全体が停止してしまいます。単一のリスナースレッドでさえも。しかし、私の理解は、このコードがワーカープールで起こっていることでした。

さて、対照的に、私が Mongoose を使用して MongoDB と話をするとき、DB の読み取りは高価な I/O 操作です。Node はその作業をスレッドに委譲し、完了したらコールバックを受け取ることができるようです。DB からの読み込みにかかる時間は、システムをブロックしないようです。

Node.jsはどのようにリスナースレッドに対してスレッドプールスレッドを使用することを決定するのでしょうか?なぜ、スリープし、スレッドプールスレッドのみをブロックするイベントコードを書くことができないのでしょうか?

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

node がどのように動作するかについてのあなたの理解は正しくありません...しかし、それはよくある誤解です。なぜなら、状況の現実は実際にはかなり複雑で、通常、物事を過度に単純化する "node is single threaded" といった小難しいフレーズにまで煮詰めてしまうからです。

当面の間、次のような明示的なマルチプロセシング/マルチスレッドを無視します。 クラスター ウェブワーカー・スレッド を追加し、典型的な非スレッドノードについて話すだけです。

Node は単一のイベントループで実行されます。 これはシングルスレッドで、あなたはその1つのスレッドしか得ることができません。 あなたが書いたすべての javascript はこのループで実行され、そのコードでブロック操作が発生すると、ループ全体がブロックされ、それが終了するまで何も起こりません。 これが、よく耳にするnodeの典型的なシングルスレッド的な性質です。 しかし、これは全体像ではありません。

特定の関数やモジュール(通常は C/C++ で書かれている)は、非同期 I/O をサポートします。 これらの関数やメソッドを呼び出すと、ワーカスレッドに呼び出しを渡すことを内部的に管理します。 例えば fs モジュールを使ってファイルをリクエストすると fs モジュールはその呼び出しをワーカスレッドに渡し、ワーカーはその応答を待ち、その間にそれなしで動いていたイベントループにそれを返します。 このすべてはノード開発者からは抽象化され、モジュール開発者からは libuv .

Denis Dollfus がコメントで指摘しているように ( この回答 からのコメント) にあるように、非同期 I/O を実現するために libuv が使用する戦略は、必ずしもスレッドプールではありません。 http モジュールの場合、現時点では別の戦略が使用されているようです。ここでの目的では、非同期コンテキストが (libuv を使用して) どのように実現されるか、そして libuv によって維持されるスレッドプールは非同期性を実現するためにそのライブラリによって提供される複数の戦略の 1 つであるということに主に注目することが重要です。


ほぼ関連する余談ですが、ノードがどのように非同期性を達成するか、また、関連する潜在的な問題とその対処法について、より深い分析があります。 この素晴らしい記事で . そのほとんどは、私が上で書いたことを拡大したものですが、さらに指摘することもあります。

  • ネイティブ C++ および libuv を使用するプロジェクトにインクルードする外部モジュールは、スレッド プールを使用する可能性があります (データベース アクセスを考えてみてください)。
  • libuv はデフォルトで 4 のスレッドプールサイズを持ち、スレッドプールへのアクセスを管理するためにキューを使用します - その結果、5 つの長時間実行する DB クエリをすべて同時に実行すると、それらの 1 つ (とスレッドプールに依存するその他の非同期アクション) は、それらのクエリが開始する前に終了するのを待っていることになります。
  • メソッドを使用してスレッド プールのサイズを大きくすることで、これを軽減することができます。 UV_THREADPOOL_SIZE 環境変数を使用することで、スレッドプールが必要となり作成される前に実行することができます。 process.env.UV_THREADPOOL_SIZE = 10;

node で伝統的なマルチプロセッシングやマルチスレッドが必要な場合は、ビルトインの cluster モジュールや他の様々なモジュール、例えば前述の webworker-threads を使うか、あるいは作業をチャンクアップする何らかの方法を実装して、手作業で setTimeout または setImmediate または process.nextTick を使うことで、作業を一時停止し、他の処理が完了するのを待ってからループで作業を続けることができます(ただし、これは推奨されません)。

もしあなたがjavascriptで長時間実行/ブロックするコードを書いているのなら、おそらく間違いであることに注意してください。 他の言語の方がはるかに効率的に実行できます。