「何十万もの」スレッドが必要になるのはいつですか?


31

Erlang、Go、およびRustはすべて、安価な「スレッド」/コルーチンを使用した同時プログラミングをサポートしていると何らかの形で主張しています。ゴーよくある質問の状態:

同じアドレス空間に数十万のゴルーチンを作成するのが実用的です。

錆チュートリアルは言います:

タスクは従来のスレッドよりも作成コストが大幅に低いため、Rustは標準的な32ビットシステムで数十万の同時タスクを作成できます。

Erlangのドキュメントによると:

233ワードのデフォルトの初期ヒープサイズは、数十万または数百万のプロセスを持つErlangシステムをサポートするためにかなり控えめです。

私の質問:どんな種類のアプリケーションが非常に多くの同時実行スレッドを必要としますか?最も忙しいWebサーバーだけが、数千もの同時訪問者を受け取ります。私が書いたボスワーカー/ジョブディスパッチタイプのアプリケーションは、スレッド/プロセスの数が物理コアの数よりもはるかに多い場合に、リターンを減少させます。数値アプリケーションにとっては理にかなっていると思いますが、実際には、ほとんどの人はこれらの新しい世代の言語ではなく、Fortran / C / C ++で書かれたサードパーティのライブラリに並列処理を委任します。


5
あなたの混乱の原因はこれだと思います:これらのマイクロスレッド/タスク/などは、主にあなたが話しているOSスレッド/プロセスの代替として意図されたものではありません。いくつかのコア間(正しく述べたように、その目的のために4つのコアに100kのスレッドを配置しても意味がありません)。
us2012

1
それから彼らは何のためにあるのでしょうか?たぶん私は世間知らずですが、コルーチンなどを導入することで単一スレッド実行プログラムが単純化されるような状況に遭遇したことはありません。そして、私はプロセスで「低」レベルの並行性を達成することができました。Linuxでは、汗をかくことなく数百または数千を起動できます。
user39019

多くのタスクを実際に機能させることはほとんど意味がありません。だからと言って、何かが起こるのを待っているだけで、ほとんどがブロックされているタスクをたくさん持つことができないというわけではありません。
ローレンペクテル

5
タスクベースの非同期性とスレッドベースの非同期性の考え方は、ユーザーコードは、それらのタスクを実行するワーカー管理するのではなく、発生する必要があるタスクに集中する必要があるということです。スレッドをあなたが雇うワーカーと考えてください。労働者を雇うことは高価であり、もしそうするなら、あなたは彼らが可能な限り多くのタスクに100%の時間で一生懸命働くことを望みます。多数のシステムは、数百または数千の保留中のタスクを持つと特徴づけることができますが、数百または数千の作業者を必要としません。
エリックリッパー

@EricLippertのコメントを続けると、数十万のタスクが存在する状況がいくつかあります。例#1:画像処理などのデータ並列タスクの分解。例#2:数十万のクライアントをサポートするサーバー。各クライアントはいつでもコマンドを発行する可能性があります。各タスクには、独自の「軽量実行コンテキスト」が必要でした。それは、現在の状態(通信プロトコル)、現在実行中のコマンドなどをほとんど覚えていない機能です。それぞれが浅い呼び出しスタックを持っている限り、軽量が可能です。
rwong

回答:


19

ユースケース-websockets:
websocketsは単純なリクエストと比較して長寿命であるため、ビジーなサーバーでは多くのwebsocketsが時間の経過とともに蓄積されます。マイクロスレッドは、優れた概念モデリングと比較的簡単な実装を提供します。

より一般的には、多数の多かれ少なかれ自律ユニットが特定のイベントの発生を待っている場合は、適切なユースケースです。


15

Erlangが元々何をするように設計されていたのかを考えるのに役立つかもしれません。ルーティング、スイッチング、センサーの収集/集約などのアクティビティ

これをウェブの世界に持ち込む-Twitterのようなシステムを考えてみてください。システムはおそらくWebページの生成にマイクロスレッドを使用しませんが、ツイートの収集/キャッシュ/配信にマイクロスレッドを使用できます。

この記事はさらに役立つ場合があります。


11

変数の変更が許可されていない言語では、状態を維持するという単純な行為には、個別の実行コンテキスト(ほとんどの人がスレッドを呼び出し、Erlangがプロセスを呼び出す)が必要です。基本的に、すべては労働者です。

カウンターを保持するこのErlang関数を考えてみましょう。

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

C ++やJavaのような従来のオブジェクト指向言語では、プライベートクラスメンバー、その状態を取得または変更するパブリックメソッド、および各カウンターのインスタンス化されたオブジェクトを持つクラスを作成することでこれを実現します。Erlangは、インスタンス化されたオブジェクトの概念をプロセスに置き換え、メソッドの概念はメッセージに、状態の維持はテール呼び出しを使用して、新しい状態を構成する値で関数を再起動します。このモデルの隠された利点(およびErlangの存在理由のほとんど)は、言語がメッセージキューを使用してカウンター値へのアクセスを自動的にシリアル化し、高度な安全性を備えた並行コードの実装を非常に簡単にすることです。 。

コンテキストスイッチは高価であるという考えにおそらく慣れているでしょう。これは、ホストOSの観点からは依然として真実です。Erlangランタイムはそれ自体が調整された小さなオペレーティングシステムであるため、OSが行うコンテキストスイッチの数を最小限に抑えながら、独自のプロセス間の切り替えは迅速かつ効率的です。このため、数千のプロセスを持つことは問題ではなく、推奨されます。


1
の最後のアプリケーションでcounter/1は小文字のcを使用する必要があります;)私はそれを修正しようとしましたが、StackExchangeは1文字の編集を好みません。
d11wtq

4

私の質問:どんな種類のアプリケーションが非常に多くの同時実行スレッドを必要としますか?

1)言語が「スケールする」という事実は、物事がより複雑になったときに、その言語を捨てる必要性が少なくなることを意味します。(これは「製品全体」の概念と呼ばれます。)多くの人々がまさにこの理由でNginxのためにApacheを捨てています。スレッドのオーバーヘッドによって課される「ハードリミット」に近い場合、怖くなり、それを乗り越える方法を考え始めます。Webサイトはトラフィック量を予測することはできません。そのため、スケーラブルにするために少し時間をかけることは合理的です。

2)リクエストごとに1つのゴルーチンが開始されます。内部でゴルーチンを使用する理由はたくさんあります。

  • 100の同時リクエストがあるWebアプリを考えてみましょう。ただし、各リクエストは100のバックエンドリクエストを生成します。明らかな例は、検索エンジンアグリゲーターです。しかし、ほとんどすべてのアプリは、画面上の各「エリア」にゴルーチンを作成し、それらを順次ではなく独立して生成できます。たとえば、Amazon.comのすべてのページは150を超えるバックエンドリクエストで構成されており、お客様専用に組み立てられています。これらは順次ではなく並列であり、各「領域」はそれ自体のWebサービスであるため、気付かないでしょう。
  • 信頼性と遅延が最も重要なアプリを検討してください。おそらく、各着信リクエストにいくつかのバックエンドリクエストを実行させ、最初に戻ってきたデータ返したいと思うでしょう。
  • アプリで行われる「クライアント参加」を検討してください。「要素ごとにデータを取得する」と言う代わりに、ゴルーチンを大量にスピンオフできます。クエリするスレーブDBがたくさんある場合、魔法のようにN倍速くなります。そうしないと、遅くなることはありません。

スレッド/プロセスの数が物理コアの数よりもはるかに大きい場合、ヒットの減少を返します

プログラムをCSPに分割する理由はパフォーマンスだけではありません。実際にプログラムを理解しやすくすることができ、いくつかの問題はより少ないコードで解決できます。

上記のリンクのスライドのように、コードに並行性を持たせることは問題を整理する方法です。ゴルーチンを持たないということは、あなたの言語でMap / Dictonary / Hashデータ構造を持たないようなものです。それなしでも大丈夫です。しかし、一度それを手に入れると、どこでもそれを使い始め、プログラムを本当に単純化します。

以前は、これは「独自のロール」マルチスレッドプログラミングを意味していました。しかし、これは複雑で危険でした-まだレースを作成していないことを確認するためのツールはあまりありません。そして、将来のメンテナーが間違いを犯さないようにするにはどうしますか?大きな/複雑なプログラムを見ると、その方向に多くのリソースを費やしていることがわかります。

並行性はほとんどの言語の第一級の部分ではないため、今日のプログラマーは、なぜ並行性が役立つのかについて盲点を持っています。これは、すべての電話と腕時計が1000コアに向かって進むにつれて明らかになります。Goには、レース検出ツールが組み込まれています。


2

Erlangの場合、接続または他のタスクごとに1つのプロセスを持つことが一般的です。したがって、たとえば、ストリーミングオーディオサーバーには、接続されたユーザーごとに1つのプロセスがあります。

Erlang VMは、コンテキストスイッチを非常に安価にすることにより、数千または数十万ものプロセスを処理するように最適化されています。


1

便利。マルチスレッドプログラミングを始めたとき、楽しみのために多くのシミュレーションとゲーム開発をサイドで行っていました。個々のオブジェクトごとにスレッドをスピンオフし、ループを介して各オブジェクトを処理するのではなく、独自のことをさせるのは非常に便利であることがわかりました。コードが非決定的な動作に邪魔されず、衝突がない場合、コーディングが簡単になります。今すぐ利用できるパワーがあるので、それに戻ってみると、十分な処理能力と多くの個別オブジェクトを処理するためのメモリがあるため、数千のスレッドがスピンオフすることを簡単に想像できます!


1

通信用に設計されたErlangの簡単な例:ネットワークパケットの転送。1つのhttp要求を行うと、数千のTCP / IPパケットが発生する場合があります。これに加えて、全員が同時に接続すると、ユースケースが得られます。

大企業が社内で注文やその他必要なものを処理するために使用する多くのアプリケーションを検討してください。スレッドを必要とするのはWebサーバーだけではありません。


-2

ここでいくつかのレンダリングタスクが思い浮かびます。画像のすべてのピクセルでopの長いチェーンを実行していて、それらのopが並列化できる場合、比較的小さな1024x768の画像でも「数十万」のブラケットに収まります。


2
数年前、私は数年かけてリアルタイムFLIR画像処理を行い、毎秒30フレームで256x256の画像を処理しました。ハードウェアプロセッサがたくさんあり、それらの間でデータを分割するシームレスな方法がない限り、最後にしたいことは、実際の計算コストにコンテキストスイッチング、メモリ競合、およびキャッシュスラッシングを追加することです。
ジョンR.ストローム

それは行われている仕事に依存します。あなたがしているのが、ジョブをハードウェアコア/実行ユニットに引き渡すことだけである場合、その後は事実上それを忘れることができます(そして、これはGPUの動作方法であるため、これは仮想シナリオではありません)有効。
マキシマスミニマス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.