この質問をする必要がある場合は、ほとんどのWebアプリケーション/サービスの機能に慣れていない可能性があります。あなたはおそらくすべてのソフトウェアがこれを行うと考えています:
user do an action
│
v
application start processing action
└──> loop ...
└──> busy processing
end loop
└──> send result to user
ただし、これはWebアプリケーション、または実際にデータベースをバックエンドとして使用するアプリケーションが機能する方法ではありません。Webアプリはこれを行います:
user do an action
│
v
application start processing action
└──> make database request
└──> do nothing until request completes
request complete
└──> send result to user
このシナリオでは、ソフトウェアはその実行時間のほとんどを0%のCPU時間を使用してデータベースが戻るのを待ちます。
マルチスレッドネットワークアプリ:
マルチスレッドネットワークアプリは、上記のワークロードを次のように処理します。
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
したがって、スレッドはほとんどの時間を0%のCPUを使用して、データベースがデータを返すのを待機しています。その間、スレッドごとに完全に別個のプログラムスタックを含むスレッドに必要なメモリを割り当てる必要がありました。また、完全なプロセスの開始ほど正確ではないスレッドを開始する必要があります。安いです。
シングルスレッドイベントループ
ほとんどの時間は0%CPUを使用しているので、CPUを使用していないときにコードを実行してみませんか?このように、各リクエストはマルチスレッドアプリケーションと同じ量のCPU時間を取得しますが、スレッドを開始する必要はありません。だから私たちはこれをします:
request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response
実際にはどちらのアプローチも、処理を支配するのはデータベースの応答時間であるため、ほぼ同じレイテンシでデータを返します。
ここでの主な利点は、新しいスレッドを生成する必要がないため、速度を低下させる大量のmallocを実行する必要がないことです。
マジック、目に見えないスレッド
一見不思議なことは、上記の両方のアプローチでワークロードを「並行」に実行する方法です。答えは、データベースはスレッド化されているということです。したがって、私たちのシングルスレッドアプリは、実際には別のプロセスであるデータベースのマルチスレッド動作を利用しています。
シングルスレッドアプローチが失敗する場所
データを返す前に多くのCPU計算を行う必要がある場合、シングルスレッドアプリは大きく失敗します。ここで、データベースの結果を処理するforループを意味するのではありません。それはまだほとんどO(n)です。つまり、フーリエ変換(mp3エンコードなど)、レイトレーシング(3Dレンダリング)などを行うことです。
シングルスレッドアプリのもう1つの落とし穴は、単一のCPUコアしか利用しないことです。したがって、クアッドコアサーバーを使用している場合(今日では珍しくありません)、他の3つのコアを使用していません。
マルチスレッドアプローチが失敗する場所
スレッドごとに大量のRAMを割り当てる必要がある場合、マルチスレッドアプリは大きく失敗します。まず、RAMの使用自体は、シングルスレッドアプリほど多くのリクエストを処理できないことを意味します。さらに悪いことに、mallocは低速です。たくさんのオブジェクト(最近のウェブフレームワークでは一般的です)を割り当てると、シングルスレッドアプリよりも遅くなる可能性があります。これが通常node.jsが勝つ場所です。
マルチスレッド化を悪化させる1つのユースケースは、スレッドで別のスクリプト言語を実行する必要がある場合です。通常、まずその言語のランタイム全体をmallocする必要があり、次にスクリプトで使用される変数をmallocする必要があります。
したがって、C、go、またはjavaでネットワークアプリを作成している場合、スレッド化のオーバーヘッドは通常それほど悪くありません。PHPまたはRubyを提供するC Webサーバーを作成している場合、JavaScriptまたはRubyまたはPythonでより高速なサーバーを作成するのは非常に簡単です。
ハイブリッドアプローチ
一部のWebサーバーはハイブリッドアプローチを使用しています。たとえばNginxとApache2は、ネットワーク処理コードをイベントループのスレッドプールとして実装します。各スレッドはイベントループを実行して、シングルスレッドのリクエストを同時に処理しますが、リクエストは複数のスレッド間で負荷分散されます。
一部のシングルスレッドアーキテクチャでもハイブリッドアプローチを使用しています。1つのプロセスから複数のスレッドを起動する代わりに、クアッドコアマシン上の4つのnode.jsサーバーなど、複数のアプリケーションを起動できます。次に、ロードバランサーを使用して、プロセス間でワークロードを分散します。
実際には、2つのアプローチは、技術的に同一の鏡像です。