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


104

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

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

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

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


@Tobi-私はそれを見ました。それでも私の質問には答えません。作業が別のスレッドで行われた場合、スリープはそのスレッドにのみ影響し、リスナーには影響しません。
ハニー

8
自分で何かを理解しようとする本当の質問、そして迷路への出口が見つからないときは、助けを求めます。
Rafael Eyng、2015年

回答:


240

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

現時点では、クラスターwebworker-threadsによる明示的なマルチプロセッシング/マルチスレッドを無視し、一般的な非スレッドノードについてのみ説明します。

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

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

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


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

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

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

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


1
神聖ながらくた、これは私のためにそれを完全に片付けます。@Jason、どうもありがとう!
ハニー

5
問題ありません:)私はあなたがそれほど昔ではないことに気づきました、そして明確な答えを見つけるのは困難でした。これまでこの種の質問をあまり深く掘り下げていなかったWeb開発者。私がCレベルに到達したとき、私の答えが技術的に100%正しいかどうかさえわかりませんが、それは大まかに正しいことです。
Jason

3
ネットワーク要求にスレッドプールを使用すると、リソースを大量に浪費することになります。この質問によると「スレッドプールなしで、epoll、kqueue、IOCPなどの異なるプラットフォームの非同期I / Oインターフェースに基づいて非同期ネットワークI / Oを実行します」-これは理にかなっています。
Denis Dollfus、2014

1
...とはいえ、メインのJavaScriptスレッドで重い処理を直接行うか、十分なリソースがないか、スレッドプールに十分なヘッドルームを提供するためにリソースを適切に管理していない場合、同時実行性が低下するとラグが発生する可能性がありますしきい値-結果として、同じシステムリソースの場合、通常、node.jsでは他のオプションよりも高いスループットが発生します(他の言語では、これに挑戦することを目的とした他のイベントベースのシステムがあります-私はしていませんただし、最近のベンチマークを参照)-イベントベースのモデルがスレッドモデルよりも優れていることは明らかです。
ジェイソン

1
@Aabidリスナースレッドはデータベースクエリを実行しないため、これらの10個のクエリすべてが完了するまでに約6秒かかります(デフォルトのスレッドプールサイズは4)。そのデータベースクエリの結果を完了する必要のないJavaScriptで作業を行う必要がある場合、たとえば、スレッドプールによる非同期作業の完了を必要としないリクエストがさらに増えた場合、メインで作業を続けます。イベントループ。
ジェイソン

20

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

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

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

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

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

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

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


1
これらの非同期APIはどこから来るのですか?私はあなたの言っていることを理解していますが、このAPIを書いた人は誰でもIOCP /非同期を選択しました。彼らはこれをどのようにして選びましたか?
ハニー

3
彼の質問は、自分の時間集約型のコードをどのように記述し、ブロックしないかです。
Jason

1
はい。ノードは、基本的なUDP、TCP、およびHTTPネットワークを提供します。非同期の「プールベース」APIのみを提供します。世界中のすべてのnode.jsコードは、利用可能なすべてのものがあるため、例外なくこれらのプールベースの非同期APIを使用します。ファイルシステムと子プロセスは別の話ですが、ネットワーキングは一貫して非同期です。
Peter Lyons 2014年

4
ピーター、気をつけて、あなたが彼のやかんのことわざの鍋にならないように。彼は、ネットワークAPIを使用する人々がそれを行う方法ではなく、ネットワークAPIの作成者がそれをどのように実行したかを知りたいと考えています。ネットワークやその他の組み込みの非同期APIとは何の関係もない独自の非ブロッキングコードを記述したかったため、最終的にノードのre-non-blockingイベントの動作を理解しました。Davidが同じことをしたいのは明らかです。
Jason

2
ノードは、それがネイティブの非ブロッキングIOを使用して、IOのためのスレッド・プールを使用していない、唯一の例外はあるfs私の知る限りでは、
vkurchatkin

2

スレッドプールの使用方法と使用者:

まず、コンピューターでノードを使用/インストールすると、コンピューター内でノードプロセスと呼ばれるプロセスが起動され、強制終了するまで実行され続けます。そして、この実行中のプロセスは、いわゆるシングルスレッドです。

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

したがって、シングルスレッドのメカニズムにより、ノードアプリケーションのブロックが容易になりますが、これはNode.jsがテーブルにもたらすユニークな機能の1つです。したがって、ノードアプリケーションを実行すると、単一のスレッドで実行されます。1人または100万人のユーザーが同時にアプリケーションにアクセスしているかどうかは関係ありません。

したがって、ノードアプリケーションを起動すると、nodejsのシングルスレッドで何が起こるかを正確に理解しましょう。最初にプログラムが初期化され、次にすべてのトップレベルコードが実行されます。これは、コールバック関数内にないすべてのコードを意味します(すべてのコールバック関数内のすべてのコードはイベントループで実行されることに注意してください)。

その後、すべてのモジュールコードが実行され、すべてのコールバックが登録されます。最後に、アプリケーションのイベントループが開始されます。

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

したがって、前に説明したように、すべてのコールバック関数とそれらの関数内のコードはイベントループで実行されます。イベントループでは、負荷はさまざまなフェーズで分散されます。とにかく、ここではイベントループについては説明しません。

スレッドプールをよりよく理解するために、イベントループで、あるコールバック関数内のコードの実行が別のコールバック関数内のコードの実行を完了した後に実行されることを想像してみてください。その後、nodejsシングルスレッドをブロックします。そのため、そこにスレッドプールが入ります。これは、イベントループのように、libuvライブラリによってNode.jsに提供されます。

したがって、スレッドプールはnodejs自体の一部ではありません。これはlibuvによって提供され、重い負荷をlibuvにオフロードします。libuvはそれらのコードを独自のスレッドで実行し、実行後、libuvはイベントループのイベントに結果を返します。

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

スレッドプールは4つの追加スレッドを提供します。これらはメインの単一スレッドから完全に分離されています。そして実際には、最大128スレッドまで構成できます。

したがって、これらすべてのスレッドが一緒になってスレッドプールを形成しました。そして、イベントループは重いタスクをスレッドプールに自動的にオフロードできます。

楽しい部分は、これらすべてが舞台裏で自動的に行われることです。スレッドプールに何を行うか、何を行わないかを決定するのは私たちの開発者ではありません。

次のようなスレッドプールに行く多くのタスクがあります。

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups

0

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.