1. ホーム
  2. multithreading

[解決済み] node.jsの並列タスクにはどっちがいいんだろう?ファイバー?Web-worker?それともThreads?

2022-10-22 17:09:35

質問

私はいつかnode.jsに出会い、それがとても気に入りました。しかし、すぐに私はそれがCPU集約的なタスクを実行する能力をひどく欠いていることを発見しました。そこで、私はググり始め、問題を解決するためにこれらの答えを得ました:Fibers、Webworkers、Threads(thread-a-gogo)です。結局のところ、IOに優れているだけで他に何もしないサーバーを持つ目的は何なのでしょうか?提案が必要です!

UPDATE。

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

というわけで、こんな感じで考えてみました。このアイデアは良いものでしょうか? 私はこのようなプロセスやスレッドに関することには少し不慣れなので、専門的な知識はありません。皆様のご意見をお聞かせください。

ありがとうございます。:)

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

Nodeは完全に異なるパラダイムを持っており、いったんそれが正しく捉えられると、問題を解決するこの異なる方法を見るのが容易になります。Nodeアプリケーションでは複数のスレッドを必要としません(1)。複数のプロセスを作成しますが、たとえば Apache Web サーバーの Prefork mpm が行う方法とは非常に異なります。

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

-- ヘイ、ハサン、あなたは初心者か、あるいは私の祖父の時代から非常に古い学校なのでしょう!

-- Hasan、あなたは初心者か、あるいは私の祖父の時代から非常に古い学校なのでしょう。

-- あ、CPUのコアが1つしかないんですけど。

-- だからなんだ?スレッドを作って、もっと速くしろ!

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

-- よしよし、あなたは貧乏なんだね。私のコンピュータを使いましょう、32コアありますよ。

-- わぁ、あなたはすごいです、私の親愛なる友人、どうもありがとうございます。感謝します!

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

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

しかし、作業がすでに定義されていて、それをどのように扱うかを完全に制御できる特定の種類の仕事においてのみ、です。しかし、それは作業がすでに定義されていて、それをどのように扱うかを完全に制御できる特定の種類の作業においてのみです。これ以外の、私たちが制御できない方法で外部から仕事がやってきて、できるだけ早く答えたいような他のすべての種類の問題については、Node の方法が間違いなく優れています。

-- おい、Hasan、まだシングルスレッドで仕事してるのか?どうしたんだ、おまえ?私はあなたが望むものを提供したまでです。あなたはもう言い訳できません。スレッドを作り、より速く走らせるのだ。

-- 作業を分割し、各工程はそのうちの1つを並行して行うようにしました。

-- なぜスレッドを作らないのですか?

-- すみません、使えないと思います。よかったらパソコン持っていってください?

-- いいえ大丈夫です、私は冷静です、ただ、なぜスレッドを使わないのかが理解できません?

-- コンピュータをありがとうございます :) 私はすでに仕事を断片に分け、その断片を並行して作業するプロセスを作っています。すべての CPU コアをフルに活用することになります。プロセスではなくスレッドでこれを行うこともできますが、Nodeにはこの方法があり、私の上司であるParth ThakkarはNodeを使うことを望んでいます。

-- もし別のコンピュータが必要なら教えてください。

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

のためにプロセッサを十分に活用することについて、私たちは今同じ考えを持っていると思います。 CPU に負荷のかかるタスク

.

-- うーん、Hasan、あなたを少し馬鹿にして申し訳ありません。私は今、あなたをよりよく理解していると思います。しかし、まだ説明が必要なことがあります。何百ものスレッドを実行することについてのすべての話題は何ですか?スレッドはプロセスをフォークするよりもはるかに高速に作成され、ダムになると、私はどこでも読みました。あなたはスレッドの代わりにプロセスをフォークし、それがNodeで得られる最高のものだと考えているのでしょう。それから、Nodeはこの種の仕事に適していないのでしょうか?

-- 心配いりません、私もクールです。誰もがこういうことを言うので、聞きなれているのだと思います。

-- それで?Nodeはダメなんですか?

-- スレッドも良いのですが、Nodeは完全に適しています。スレッドやプロセス生成のオーバーヘッドについては、何度も繰り返すようなものでは、1ミリ秒単位でカウントされます。しかし、私は32個のプロセスしか作らないので、ほんのわずかな時間しかかかりません。一度だけです。それは何の違いも生じないでしょう。

-- では、何千ものスレッドを作りたいときはどうすればいいのでしょうか?

-- 何千ものスレッドを作りたいとは思わないでしょう。しかし、HTTPリクエストを処理するWebサーバーのように、外部からやってくる仕事をするシステムでは、リクエストごとにスレッドを使用する場合、たくさんのスレッドを作成することになります。

-- Nodeは違いますが?そうでしょう?

-- そうです、その通りです。ここがNodeが本当に輝いているところです。スレッドがプロセスよりずっと軽いように、関数呼び出しはスレッドよりずっと軽いのです。Nodeはスレッドを作成する代わりに関数を呼び出します。Webサーバーの例では、入ってくるリクエストはすべて関数呼び出しを引き起こします。

-- しかし、複数のスレッドを使用しない場合、同時に実行できるのは1つの関数だけです。ウェブサーバーにたくさんのリクエストが同時に届いたとき、これはどのように機能するのでしょうか?

-- 関数の実行方法については完全に正しく、一度にひとつずつで、決してふたつ並行して実行することはありません。つまり、1 つのプロセスでは、一度に実行されるコードのスコープは 1 つだけです。OS のスケジューラーがやってきて、この関数を一時停止して、別の関数に切り替えるということはありません。(2)

-- では、1つのプロセスで同時に2つの要求を処理することはできるのでしょうか?

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

-- うーん、今更ながら興奮するべきか?

-- そうかもしれませんね。) Node はキューに対してループを実行します。このキューには私たちのジョブ、つまり入ってくるリクエストを処理するために始めたコールが入っています。ここで最も重要な点は、関数を実行するように設計する方法です。リクエストの処理を開始して、呼び出し元をジョブが終了するまで待たせるのではなく、許容できる量の仕事をした後に関数を素早く終了させるのです。他のコンポーネントが何らかの処理を行って値を返してくるのを待つ必要がある場合、それを待つ代わりに、残りの作業をキューに追加して関数を終了させます。

-- 複雑すぎる?

-- しかし、このシステム自体は非常にシンプルで、完全に理にかなっています。

では、この2人の開発者の対話を引用するのはやめて、最後にこれらの機能がどのように機能するかを簡単に例示して、私の回答を終えたいと思います。

このように、私たちはOS Schedulerが通常行うことを行っているのです。ある時点で作業を一時停止し、他の関数呼び出し (マルチスレッド環境における他のスレッドのようなもの) を、再び自分の番が回ってくるまで実行させています。これは、システム上のすべてのスレッドにちょうどよい時間を与えようとするOS Schedulerに仕事を任せるよりもずっとよい方法です。私たちは 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 イベントを受け取ったり、プロセス間のメッセージングを処理したりするためのものです。

UPDATE (コメント中の良い質問に対する返答として)

Mark、建設的な批評をありがとうございます。Nodeのパラダイムでは、キュー内の他のすべての呼び出しが次々に実行されるように設計されていない限り、処理に時間がかかりすぎる関数は決してあってはなりません。計算量の多いタスクの場合、全体を俯瞰すると、「スレッドとプロセスのどちらを使うべきか」という問題ではなく、「これらのタスクをいかにバランスよく分割し、システムの複数のCPUコアを用いて並列実行できるか」という問題であることが分かります。一度に1つのファイルを処理する場合、同じファイルの異なる部分を処理するシステムが必要になりますが、その場合、マルチスレッドのシングルプロセスシステムを構築する方が簡単で、さらに効率的でしょう。この場合でも、複数のプロセスを実行し、状態共有や通信が必要な場合はそれらの間でメッセージを受け渡しすることで、Nodeを使用することができます。先ほども言ったように、Nodeを使ったマルチプロセスアプローチは と同様に この種のタスクにおけるマルチスレッドアプローチと同様ですが、それ以上ではありません。繰り返しになりますが、Nodeが輝くのは、これらのタスクが複数のソースからシステムに入力される場合です。なぜならNodeでは、スレッドパーコネクションやプロセスパーコネクションのシステムに比べて、多くの接続を同時に保つことがずっと軽いからです。

に関しては setTimeout(...,0) の呼び出しについては、キューの中の呼び出しに処理を分担させるために、時間のかかるタスクの間に休憩を入れることが必要な場合があります。しかし、これはハックではなく、イベントキューが機能する方法なのです。また、イベントキューで process.nextTick を使うのは、より良い方法です。 setTimeout というように、時間経過の計算とチェックが必要です。 process.nextTick は単に私たちが本当に欲しいものです: "おいタスク、キューの最後に戻れ。