web-dev-qa-db-ja.com

スレッドプールはいつ使用されますか?

そのため、Node.jsの仕組みを理解しています。イベントを受け取り、それをワーカープールに委任する単一のリスナースレッドがあります。ワーカースレッドは、作業が完了するとリスナーに通知し、リスナーは呼び出し元に応答を返します。

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

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

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

92
Haney

ノードがどのように機能するかについてのあなたの理解は正しくありません...しかし、状況の現実は実際にはかなり複雑で、一般的に物事を単純化する「ノードはシングルスレッド」のような簡潔な小さなフレーズに要約されるため、それは一般的な誤解です。

今のところ、 cluster および webworker-threads による明示的なマルチプロセッシング/マルチスレッドを無視し、典型的な非スレッドノードについてのみ説明します。

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

通常C/C++で記述された特定の関数とモジュールは、非同期I/Oをサポートします。これらの関数とメソッドを呼び出すと、ワーカースレッドへの呼び出しの受け渡しが内部的に管理されます。たとえば、fsモジュールを使用してファイルを要求すると、fsモジュールはその呼び出しをワーカースレッドに渡し、そのワーカーはその応答を待ってからイベントに返しますその間、それなしでかき回しているループ。これらはすべて、ノード開発者であるあなたから抽象化されており、一部は libuv を使用してモジュール開発者から抽象化されています。

コメントでデニス・ドルファスが指摘したように( この回答 から同様の質問へ)、非同期I/Oを達成するためにlibuvが使用する戦略は、特にhttpモジュールは、現時点では異なる戦略が使用されているようです。ここでの目的のためには、非同期コンテキストが(libuvを使用して)達成される方法と、libuvによって維持されるスレッドプールが非同期性を達成するためにそのライブラリによって提供される複数の戦略の1つであることに注意することが主に重要です。


主に関連する接線では、ノードが非同期性を達成する方法、およびいくつかの関連する潜在的な問題とその対処方法について、より深い分析があります この優れた記事 。それのほとんどは私が上で書いたものを拡張しますが、さらにそれは指摘します:

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

ノードで従来のマルチプロセッシングまたはマルチスレッド化が必要な場合は、組み込みのclusterモジュールまたは前述のwebworker-threadsなどのさまざまなモジュールを介して取得できます。作業をsetTimeoutまたはsetImmediateまたはprocess.nextTickを使用して手動で実行して作業を一時停止し、後のループで続行して他のプロセスを完了させます(ただし、これはお勧めしません)。

JavaScriptで長時間実行/ブロックするコードを書いている場合、おそらく間違いを犯していることに注意してください。他の言語は、はるかに効率的に実行されます。

224
Jason

そのため、Node.jsの仕組みを理解しています。イベントを受け取り、それをワーカープールに委任する単一のリスナースレッドがあります。ワーカースレッドは、作業が完了するとリスナーに通知し、リスナーは呼び出し元に応答を返します。

これは本当に正確ではありません。 Node.jsには、JavaScriptの実行を行う単一の「ワーカー」スレッドのみがあります。ノード内にはIO処理を処理するスレッドがありますが、それらを「ワーカー」と考えるのは誤解です。本当にIO処理とノードの内部実装の他のいくつかの詳細がありますが、プログラマーとして、MAX_LISTENERSなどのいくつかのその他のパラメーター以外の動作に影響を与えることはできません。

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

JavaScriptにはスリープメカニズムはありません。 「スリープ」の意味を考えるコードスニペットを投稿した場合、これをより具体的に説明できます。たとえば、Pythonのtime.sleep(30)のようなものをシミュレートするために呼び出すような関数はありません。 setTimeoutがありますが、基本的にはスリープではありません。 setTimeoutおよびsetIntervalは明示的にrelease、イベントループをブロックせず、コードのその他のビットがメイン実行スレッドで実行できるようにします。できることは、メモリ内の計算でCPUをビジーループすることだけです。これにより、メインの実行スレッドが実際に不足し、プログラムが応答しなくなります。

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

ネットワークIOは常に非同期です。物語の終わり。ディスクIOには同期APIと非同期APIの両方があるため、「決定」はありません。 node.jsは、syncと通常の非同期を呼び出すAPIコア関数に従って動作します。例:fs.readFile vs fs.readFileSync。子プロセスの場合は、child_process.exec AP​​Iとchild_process.execSync AP​​Iも別々にあります。

経験則では、常に非同期APIを使用します。同期APIを使用する正当な理由は、接続をリッスンする前のネットワークサービスの初期化コード、またはビルドツールなどのネットワーク要求を受け入れない単純なスクリプトのためです。

18
Peter Lyons

この誤解は、プリエンプティブマルチタスクと協調マルチタスクの違いにすぎません...

本当にすべての乗り物に1つの行があるため、睡眠はカーニバル全体をオフにし、ゲートを閉じました。 「JSインタープリターとその他の何か」と考えて、スレッドを無視します...

...ブロックしないでください。

0