113

少し前に node.js に出会い、とても気に入りました。しかしすぐに、CPU を集中的に使用するタスクを実行する能力が著しく欠けていることがわかりました。それで、私はグーグルを始めて、問題を解決するためにこれらの答えを得ました:Fibers、Webworkers、Threads(thread-a-gogo)。どちらを使用するかは混乱しており、そのうちの 1 つを使用する必要があります。結局のところ、IO だけが得意で他に何もないサーバーを使用する目的は何ですか? 提案が必要です!

アップデート:

私は遅刻する方法を考えていました。それに対する提案が必要なだけです。さて、私が考えたのはこれでした: いくつかのスレッドを用意しましょう (thread_a_gogo または webworkers を使用)。より多くのものが必要な場合は、さらに作成できます。ただし、作成プロセスにはいくつかの制限があります。(システムによって暗示されているわけではありませんが、おそらくオーバーヘッドが原因です)。これで、制限を超えたときに、新しいノードをフォークして、その上にスレッドの作成を開始できます。このようにして、ある程度の限界に達するまで続けることができます (結局のところ、プロセスにも大きなオーバーヘッドがあります)。この制限に達すると、タスクのキューイングが開始されます。スレッドが解放されるたびに、新しいタスクが割り当てられます。このように、スムーズに進めることができます。

それで、それは私が考えたことでした。このアイデアは良いですか?私はこのプロセスとスレッドのすべてに少し慣れていないので、専門知識はありません。ご意見をお聞かせください。

ありがとう。:)

4

7 に答える 7

332

ノードにはまったく異なるパラダイムがあり、正しくキャプチャされると、問題を解決するこの異なる方法を簡単に確認できます。同じことを別の方法で行うため、Node アプリケーション (1) で複数のスレッドが必要になることはありません。複数のプロセスを作成します。しかし、たとえば Apache Web Server の Prefork mpm とは大きく異なります。

今のところ、CPU コアが 1 つしかないと考えて、何らかの作業を行うアプリケーションを (ノードの方法で) 開発します。私たちの仕事は、その内容をバイト単位で実行する大きなファイルを処理することです。私たちのソフトウェアにとって最善の方法は、ファイルの先頭から作業を開始し、バイト単位で最後まで作業を進めることです。

-- ヘイ、ハサン、あなたは私の祖父の時代からの初心者か非常に古い学校のどちらかだと思います!!! いくつかのスレッドを作成して、はるかに高速にしてみませんか?

-- あ、CPU コアが 1 つしかありません。

- だから何?いくつかのスレッドを作成して、高速化してください!

――そうはいきません。スレッドを作成すると、スレッドが遅くなります。スレッド間の切り替えのためにシステムに多くのオーバーヘッドを追加するため、スレッドに適切な時間を与えようとし、プロセス内でこれらのスレッド間の通信を試みます。これらすべての事実に加えて、1 つのジョブを並行して実行できる複数の部分に分割する方法についても考える必要があります。

-- わかりました、わかりました、あなたは貧乏です。私のコンピューターを使ってみましょう。32 コアを搭載しています。

-- うわー、あなたは素晴らしい私の親愛なる友人です、どうもありがとう。それは有り難いです!

それから私たちは仕事に戻ります。私たちの豊かな友人のおかげで、今では 32 個の CPU コアがあります。私たちが守らなければならないルールが変わったばかりです。今、私たちは与えられたすべての富を活用したいと考えています。

複数のコアを使用するには、作業を分割して並列処理できるようにする方法を見つける必要があります。ノードでない場合は、これにスレッドを使用します。各 CPU コアに 1 つずつ、32 スレッド。ただし、Node があるため、32 個の Node プロセスを作成します。

スレッドは、Node プロセスの優れた代替手段になる可能性があります。ただし、作業がすでに定義されており、その処理方法を完全に制御できる特定の種類のジョブのみです。これ以外に、私たちが制御できない方法で仕事が外部からもたらされ、できるだけ早く答えたいと考えている他のすべての種類の問題については、ノードの方法が間違いなく優れています。

-- ハサンさん、まだシングル スレッドで作業していますか? 何が悪いの?私はちょうどあなたが望んでいたものを提供しました。もう言い訳はありません。スレッドを作成し、より高速に実行します。

-- 作業をいくつかの部分に分割しました。すべてのプロセスは、これらの部分の 1 つに対して並行して実行されます。

――スレッドを立ててみませんか?

-- すいません、使えないと思います。必要に応じて、コンピューターを使用できますか?

-- いいえ、私はクールです。なぜあなたがスレッドを使用しないのか理解できませんか?

-- パソコンありがとうございます。:) 私はすでに作業をいくつかの部分に分割しており、これらの部分を並行して処理するプロセスを作成しています。すべての CPU コアが完全に使用されます。プロセスではなくスレッドでこれを行うことができました。しかし、Nodeにはこの方法があり、上司のParth ThakkarはNodeを使用することを望んでいます。

-- わかりました、別のコンピューターが必要な場合はお知らせください。:p

32 ではなく 33 のプロセスを作成すると、オペレーティング システムのスケジューラはスレッドを一時停止し、別のスレッドを開始し、数サイクル後に一時停止し、別のスレッドを再度開始します...これは不要なオーバーヘッドです。いりません。実際、32 個のコアを持つシステムでは、正確に 32 個のプロセスを作成したくはありません。31個の方が適切です。このシステムで動作するのは私のアプリケーションだけではないからです。特に部屋が 32 室ある場合は、他のもののために少しスペースを空けておくとよいでしょう。

CPU を集中的に使用するタスクにプロセッサを最大限に活用することについて、私たちは今、同じページにいると思います。

――うーん、ハサンさん、ちょっとからかってすみません。私は今あなたをよりよく理解していると信じています。しかし、まだ説明が必要なことがあります。何百ものスレッドを実行することについて話題になっているのは何ですか? プロセスをフォークするよりもスレッドの作成がはるかに高速で、愚かであることをどこでも読みましたか? スレッドの代わりにプロセスを fork し、Node.js で得られる最高のものだと思います。では、Nodeはこの種の作業には適していませんか?

――ご安心ください、私もかっこいいです。こういうことはみんな言うので、聞き慣れていると思います。

- そう?ノードはこれには適していませんか?

-- スレッドも良いかもしれませんが、ノードはこれに完全に適しています。スレッド/プロセス作成のオーバーヘッドについては; 何度も繰り返すことでは、1 ミリ秒も重要ではありません。ただし、作成するプロセスは 32 個しかなく、わずかな時間がかかります。それは一度だけ起こります。違いはありません。

-- では、何千ものスレッドを作成したいのはいつですか?

-- 何千ものスレッドを作成したくありません。ただし、HTTP 要求を処理する Web サーバーのように、外部からの作業を行っているシステムでは、リクエストごとにスレッドを使用している場合は、多くのスレッドを作成することになります。

――ノードが違うんですけど?右?

- はい、正確に。これが Node の真価を発揮するところです。スレッドがプロセスよりもはるかに軽量であるように、関数呼び出しはスレッドよりもはるかに軽量です。ノードは、スレッドを作成する代わりに関数を呼び出します。Web サーバーの例では、着信要求ごとに関数呼び出しが発生します。

-- うーん、興味深いです。ただし、複数のスレッドを使用していない場合は、同時に 1 つの関数しか実行できません。多数のリクエストが同時に Web サーバーに到着した場合、これはどのように機能しますか?

-- 関数が一度に 1 つずつ実行され、2 つの関数が同時に実行されることはありません。つまり、1 つのプロセスでは、一度に実行されるコードのスコープは 1 つだけです。OS スケジューラは、プロセス内の別のスレッドではなく、別のプロセスに時間を与えるためにプロセスを一時停止しない限り、この関数を一時停止して別の関数に切り替えることはありません。(2)

-- それでは、プロセスが一度に 2 つのリクエストを処理するにはどうすればよいでしょうか。

-- システムに十分なリソース (RAM、ネットワークなど) がある限り、プロセスは一度に数万のリクエストを処理できます。これらの機能がどのように実行されるかが重要な違いです。

――うーん、今ドキドキしていいですか?

-- たぶん :) ノードはキューをループします。このキューにはジョブ、つまり着信リクエストを処理するために開始した呼び出しがあります。ここで最も重要な点は、関数を実行するように設計する方法です。リクエストの処理を開始し、ジョブが完了するまで呼び出し元を待たせる代わりに、許容できる量の作業を行った後、関数をすぐに終了します。別のコンポーネントが何らかの作業を行って値を返すのを待つ必要がある場合、それを待つ代わりに、残りの作業をキューに追加して関数を終了するだけです。

--複雑すぎませんか?

-- いいえ、複雑に聞こえるかもしれません。しかし、システム自体は非常にシンプルで、理にかなっています。

ここで、この 2 人の開発者の間の対話を引用するのをやめ、これらの関数がどのように機能するかを示す最後の簡単な例を示した後で、回答を締めくくりたいと思います。

このようにして、OS スケジューラが通常行うことを行っています。ある時点で作業を一時停止し、別の関数呼び出し (マルチスレッド環境の他のスレッドなど) を次の順番になるまで実行させます。これは、システム上のすべてのスレッドに時間を与えようとする OS スケジューラに作業を任せるよりもはるかに優れています。私たちは、OS スケジューラよりもはるかによく何をしているかを知っており、停止すべきときに停止することが期待されています。

以下は、ファイルを開いて読み取り、データに対して何らかの作業を行う簡単な例です。

同期方法:

Open File
Repeat This:    
    Read Some
    Do the work

非同期の方法:

Open File and Do this when it is ready: // Our function returns
    Repeat this:
        Read Some and when it is ready: // Returns again
            Do some work

ご覧のとおり、この関数はシステムにファイルを開くように要求し、ファイルが開かれるのを待ちません。ファイルの準備ができたら、次のステップを提供することで終了します。戻ると、Node はキューで他の関数呼び出しを実行します。すべての関数を実行した後、イベント ループは次のターンに移動します...

要約すると、Node にはマルチスレッド開発とはまったく異なるパラダイムがあります。しかし、これは物事が欠けているという意味ではありません。同期ジョブ (処理の順序と方法を決定できる場合) の場合、マルチスレッド並列処理と同様に機能します。サーバーへのリクエストのように外部から来るジョブの場合、それは単に優れています。


(1) C/C++ などの他の言語でライブラリを構築している場合を除きます。その場合でも、ジョブを分割するためのスレッドは作成しません。この種の作業では、2 つのスレッドがあり、そのうちの 1 つは Node との通信を継続し、もう 1 つは実際の作業を行います。

(2) 実際、最初の脚注で述べたのと同じ理由で、すべての Node プロセスには複数のスレッドがあります。ただし、これは 1000 のスレッドが同様の作業を行うようなものではありません。これらの余分なスレッドは、IO イベントを受け入れたり、プロセス間メッセージングを処理したりするためのものです。

更新 (コメントの良い質問への返信として)

@マーク、建設的な批判をありがとう。Node のパラダイムでは、キュー内の他のすべての呼び出しが次々に実行されるように設計されていない限り、処理に時間がかかりすぎる関数を使用するべきではありません。計算コストの高いタスクの場合、全体像を見ると、これは「スレッドまたはプロセスを使用する必要があるか」という問題ではないことがわかります。しかし、「これらのタスクをバランスよくサブタスクに分割し、システム上で複数の CPU コアを使用して並行して実行するにはどうすればよいか」という問題があります。8 コアのシステムで 400 個のビデオ ファイルを処理するとします。一度に 1 つのファイルを処理する場合は、同じファイルのさまざまな部分を処理するシステムが必要です。その場合、おそらく、マルチスレッドの単一プロセス システムを構築する方が簡単で、さらに効率的です。状態共有/通信が必要な場合は、複数のプロセスを実行し、それらの間でメッセージを渡すことにより、これに Node を引き続き使用できます。前に述べたように、Node を使用したマルチプロセス アプローチは、この種のタスクでのマルチスレッド アプローチと同様に。しかし、それ以上ではありません。繰り返しますが、前に述べたように、ノードが輝く状況は、これらのタスクが複数のソースからシステムへの入力として来る場合です。ノードでは、接続ごとのスレッドまたは接続ごとのプロセスと比較して、多くの接続を同時に維持する方がはるかに軽いからです。システム。

通話に関してsetTimeout(...,0)は; 時間のかかるタスク中に休憩を与えて、キュー内の呼び出しが処理を共有できるようにすることが必要になる場合があります。タスクをさまざまな方法で分割することで、これらの問題を回避できます。ただし、これは実際にはハックではなく、イベント キューが機能する方法です。また、process.nextTickこの目的で を使用することは、 を使用するsetTimeoutときに経過時間の計算とチェックが必要になるため、はるかに優れprocess.nextTickています。 "

于 2012-07-01T00:25:47.423 に答える
8

私は、マルチスレッドを使用してソフトウェアを高速化するという古い考え方から来ました。過去 3 年間、私は Node.js を使用しており、それを大いに支持しています。hasanyasin が、ノードの仕組みと非同期機能の概念について詳しく説明したように。しかし、ここでいくつか追加させてください。

シングルコアと低いクロック速度の時代にさかのぼり、ソフトウェアを高速かつ並列に動作させるためにさまざまな方法を試しました。DOS 時代には、一度に 1 つのプログラムを実行していました。Windows よりも、複数のアプリケーション (プロセス) を一緒に実行するようになりました。プリエンプティブおよび非プリエンプティブ (または協調的) などの概念がテストされました。シングル コア コンピューターでより優れたマルチプロセッシング タスクを実現するには、プリエンプティブが答えであることがわかりました。それに伴い、プロセス/タスクとコンテキスト切り替えの概念が生まれました。スレッドの概念よりも、プロセスのコンテキスト切り替えの負担をさらに軽減します。新しいプロセスを生成するための軽量の代替手段として造られたスレッド。

したがって、スレッドに信号を送るかどうか、マルチコアまたはシングルコアでないかどうかに関係なく、プロセスは OS によってプリエンプトされ、タイム スライスされます。

Nodejs は単一のプロセスであり、非同期メカニズムを提供します。ここでは、タスクが終了するのをイベント ループで待機している間に、下にある OS にジョブがディスパッチされてタスクが実行されます。OS からグリーン信号を受け取ったら、必要なことをすべて実行します。ある意味では、これは協調的/非プリエンプティブ マルチタスクであるため、イベント ループを非常に長い時間ブロックしないでください。そうしないと、アプリケーションが非常に速く劣化します。
したがって、本質的にブロックしているタスクや非常に時間がかかるタスクがある場合は、OS とスレッドのプリエンプティブな世界に分岐する必要があります。これの良い例がlibuv documentationにあります。また、ドキュメントをさらに読むと、FileI/O が node.js のスレッドで処理されることがわかります。

そのため、まず、ソフトウェアの設計にすべてがかかっています。第二に、コンテキストの切り替えは、彼らがあなたに何を言っても、常に起こっています. スレッドが存在し、それには理由があります。その理由は、プロセス間で切り替える方が高速だからです。

node.js のフードの下には、すべての C++ とスレッドがあります。ノードは、その機能を拡張し、必要なスレッドを使用してさらに高速化する C++ の方法を提供します。つまり、ソースからソースへの書き込み、大規模なデータ分析などのタスクをブロックします。

hasanyasinの答えが受け入れられていることは知っていますが、私にとっては、あなたが何を言おうと、またはスクリプトの背後にそれらを隠す方法に関係なく、スレッドが存在します。また、スレッドは Node.js のバックボーンにあるため、マルチスレッドを完全に打ち破る前は正しくありません。また、スレッドはプロセスとは異なり、コアごとにノード プロセスを持つという制限はスレッドの数には正確には適用されません。スレッドはプロセスのサブタスクのようなものです。実際、スレッドは Windows のタスク マネージャーや Linux の top コマンドには表示されません。繰り返しになりますが、プロセスよりも軽量です

于 2015-01-27T07:10:13.203 に答える
4

この場合、webworkers が関連しているかどうかはわかりません。node.js はサーバー上で実行されますが、それらはクライアント側の技術 (ブラウザーで実行) です。私が理解している限り、ファイバーもブロックしています。つまり、ファイバーは自発的なマルチタスクであるため、使用できますが、 を介してコンテキストスイッチを自分で管理する必要がありますyield。スレッドは実際に必要なものかもしれませんが、node.js でどれだけ成熟しているかはわかりません。

于 2012-05-27T11:21:39.877 に答える
2

多くのノード開発者の意見では、ノードの最良の部分の1つは、実際にはそのシングルスレッドの性質です。スレッドは、ノードが非ブロッキングIOのみを実行することで完全に回避する、共有リソースに関する多くの問題を引き起こします。

それは、ノードが単一のスレッドに制限されているということではありません。スレッド化された並行性を取得する方法が、探しているものとは異なるだけです。スレッドを処理する標準的な方法は、ノード自体に標準で付属しているクラスターモジュールを使用することです。これは、コードでスレッドを手動で処理するよりも、スレッドへのより簡単なアプローチです。

コードで非同期プログラミングを処理する場合(ネストされたコールバックピラミッドを回避する場合など)、Fibersライブラリの[Future]コンポーネントは適切な選択です。また、FibersをベースにしたAsyncblockをチェックすることをお勧めします。ファイバーは、スタックを複製し、必要に応じてシングルスレッドでスタック間をジャンプすることで、コールバックを非表示にできるので便利です。メリットを提供しながら、実際のスレッドの煩わしさを軽減します。欠点は、ファイバーを使用するとスタックトレースが少し奇妙になる可能性があることですが、それほど悪くはありません。

非同期のことを心配する必要がなく、ブロックせずに多くの処理を実行することに関心がある場合は、process.nextTick(callback)をたまに呼び出すだけで十分です。

于 2012-05-27T17:03:59.873 に答える
1

おそらく、実行しているタスクに関する詳細情報が役立つでしょう。何千ものそれらを作成する必要があるのはなぜですか(ジェネリックデイブの回答へのコメントで述べたように)?Node でこの種のことを行う通常の方法は、常に実行され、メッセージを使用して通信できるワーカー プロセスを (フォークまたはその他の方法を使用して) 起動することです。つまり、実行中のタスクを実行する必要があるたびに新しいワーカーを起動するのではなく、既に実行中のワーカーにメッセージを送信し、完了時に応答を取得するだけです。正直なところ、何千もの実際のスレッドを起動することが非常に効率的であるとは思えません。まだ CPU によって制限されています。

さて、それをすべて言った後、私は最近Hook.ioで多くの作業を行ってきました.これは、この種のタスクを他のプロセスにオフロードするのに非常にうまく機能しているようです.必要なものを達成できるかもしれません.

于 2012-06-30T09:36:02.677 に答える