スレッドはコードの実行を高速化できますが、実際には必要ですか?すべてのコードを単一のスレッドを使用して実行できますか、または複数のスレッドを使用してのみ達成できる何かが存在しますか?
スレッドはコードの実行を高速化できますが、実際には必要ですか?すべてのコードを単一のスレッドを使用して実行できますか、または複数のスレッドを使用してのみ達成できる何かが存在しますか?
回答:
まず第一に、スレッドはコードの実行速度を上げることができません。それらはコンピューターをより速く走らせません。彼らができることは、それ以外の場合は無駄になる時間を使用して、コンピューターの効率を高めることです。特定のタイプの処理では、この最適化により効率が向上し、実行時間が短縮されます。
簡単な答えはイエスです。単一のスレッドで実行される任意のコードを作成できます。証明:シングルプロセッサシステムは、命令を直線的にしか実行できません。複数行の実行は、オペレーティングシステムが割り込みを処理し、現在のスレッドの状態を保存し、別のスレッドを開始することによって行われます。
複雑な答えは...複雑です!マルチスレッドプログラムが線形プログラムよりも効率的である場合が多いのは、ハードウェアの「問題」が原因です。CPUは、メモリやハードストレージIOよりも高速に計算を実行できます。したがって、たとえば、「追加」命令は「フェッチ」よりもはるかに高速に実行されます。キャッシュと専用プログラム命令のフェッチ(ここでの正確な用語は不明)は、ある程度これに対処できますが、速度の問題は残ります。
スレッドは、IO命令の完了中にCPUバウンド命令にCPUを使用することにより、この不一致に対処する方法です。典型的なスレッド実行計画は、おそらく次のとおりです。データの取得、データの処理、データの書き込み。説明のために、フェッチと書き込みに3サイクルかかり、処理に1サイクルかかるとします。コンピューターが読み取りまたは書き込みを行っている間、それぞれ2サイクルの間何もしていないことがわかりますか?明らかに怠laであり、最適化の鞭を解く必要があります!
この無駄な時間を使用するために、スレッドを使用してプロセスを書き換えることができます。
等々。明らかにこれはやや不自然な例ですが、この手法がIOの待機に費やす時間をどのように活用できるかを見ることができます。
上記のスレッド化は、IOに強くバインドされたプロセスでのみ効率を向上できることに注意してください。プログラムが主に物事を計算している場合、より多くの作業を行うことができる「穴」はあまりありません。また、スレッドを切り替えるときにいくつかの命令のオーバーヘッドがあります。実行するスレッドが多すぎると、CPUはほとんどの時間を切り替えに費やし、実際にはあまり問題に取り組んでいません。これはスラッシングと呼ばれます。
これはすべてシングルコアプロセッサには適していますが、最新のプロセッサのほとんどは2つ以上のコアを備えています。スレッドは、CPUの使用を最大化するという同じ目的を果たしますが、今回は2つの個別の命令を同時に実行することができます。これは、コンピューターが実際にコンテキスト切り替えではなくマルチタスクを実行しているため、使用可能なコアの数だけ実行時間を短縮できます。
複数のコアでは、スレッドは2つのコア間で作業を分割する方法を提供します。ただし、上記は個々のコアにも適用されます。1つのコアで2つのスレッドで最大の効率を実行するプログラムは、2つのコアで約4つのスレッドで最大の効率で実行される可能性が高くなります。(効率はここで最小NOP命令の実行によって測定されます。)
(シングルコアではなく)複数のコアでスレッドを実行する場合の問題は、一般にハードウェアによって処理されます。CPUは、読み取り/書き込みを行う前に適切なメモリ位置をロックしていることを確認します。(これにはメモリ内の特別なフラグビットを使用することを読みましたが、これはいくつかの方法で実現できます。)高級言語を使用するプログラマーとして、2つのコアでこれ以上何も心配する必要はありません。 1つにする必要があります。
TL; DR:スレッドは、コンピューターが複数のタスクを非同期に処理できるように作業を分割できます。これにより、プロセスがリソースを待機しているときにロックするのではなく、利用可能なすべての処理時間を利用して、コンピューターを最大効率で実行できます。
単一のスレッドではできない複数のスレッドで何ができますか?
なし。
簡単な証明スケッチ:
ただし、そこには大きな前提が隠されていることに注意してください。つまり、単一スレッド内で使用される言語はチューリング完全であるということです。
したがって、より興味深い質問は、「マルチスレッドをチューリング完全でない言語に追加するだけでチューリング完全にできるのでしょうか?」です。そして、答えは「はい」だと思います。
Total Functional Languagesを見てみましょう。[慣れていない人のために:関数型プログラミングが関数を使ったプログラミングであるように、Total Functional Programmingは合計関数を使ったプログラミングです。]
Total Functional Languagesは明らかにチューリング完全ではありません:TFPLで無限ループを書くことはできません(実際、それはほとんど「合計」の定義です)が、チューリングマシンではできます。TFPLで作成することはできませんが、UTMで作成することはできます。そのため、TFPLはUTMよりも計算的に強力ではありません。
ただし、TFPLにスレッドを追加するとすぐに、無限ループが発生します。ループの各反復を新しいスレッドで実行するだけです。個々のスレッドは常に結果を返すため、Totalになりますが、すべてのスレッドは次の反復を無限に実行する新しいスレッドも生成します。
私が考えるこの言語はチューリング完全になること。
少なくとも、元の質問に答えます:
単一のスレッドではできない複数のスレッドで何ができますか?
無限ループを実行できない言語がある場合、マルチスレッドを使用すると無限ループを実行できます。
もちろん、スレッドの生成は副作用であるため、拡張言語はTotalであるだけでなく、機能的でもないことに注意してください。
理論的には、マルチスレッドプログラムが行うすべての処理は、シングルスレッドプログラムでも実行できますが、処理速度は遅くなります。
実際には、速度の差は非常に大きいため、タスクにシングルスレッドプログラムを使用する方法はありません。たとえば、毎晩実行するバッチデータ処理ジョブがあり、単一のスレッドで完了するのに24時間以上かかる場合、マルチスレッドにする以外のオプションはありません。(実際には、しきい値はおそらくさらに小さくなります。多くの場合、このような更新タスクは、ユーザーがシステムを再び使用し始める前に早朝までに終了する必要があります。使用可能なランタイムは数時間/分程度である場合があります。)
複数のスレッドでコンピューティング作業を行うことは、分散処理の一種です。作業を複数のスレッドに分散しています。分散処理の別の例(複数のスレッドの代わりに複数のコンピューターを使用)は、SETIスクリーンセーバーです:単一のプロセッサーで大量の測定データを処理するには非常に長い時間がかかり、研究者は退職前に結果を確認することを好みます;-)ただし、スーパーコンピュータをそれほど長く借りる予算がないので、何百万台もの家庭用PCにジョブを分散させて、安価にしています。
スレッドは逐次計算からの小さなステップのように見えますが、実際には、それらは大きなステップを表しています。逐次計算の最も重要で魅力的な特性である理解可能性、予測可能性、および決定論を破棄します。計算のモデルとしてのスレッドは非常に非決定的であり、プログラマーの仕事はその非決定性を排除することの1つになります。
-スレッドの問題(www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf)。
スレッドを使用すると、複数のコアに作業を分散できるというパフォーマンス上の利点がいくつかありますが、多くの場合、非常に高価です。
ここでまだ言及されていないスレッドを使用することの欠点の1つは、シングルスレッドプロセススペースで得られるリソースの区分化が失われることです。たとえば、セグメンテーション違反のケースに遭遇したとします。場合によっては、マルチプロセスアプリケーションでこれから回復することができます。その場合、フォールトしている子を死に至らしめ、新しい子を再スポーンします。これは、Apacheのプリフォークバックエンドの場合です。1つのhttpdインスタンスがいっぱいになると、最悪のケースとして、特定のHTTPリクエストがそのプロセスに対してドロップされる場合がありますが、Apacheは新しい子を生成します。最終結果は、Apache全体が障害のあるスレッドによって削除されないことです。
このシナリオのもう1つの考慮事項は、メモリリークです。スレッドクラッシュを適切に処理できる場合があります(UNIXでは、特定の信号(segfault / fpviolationでさえも)からの回復が可能です)が、その場合でも、そのスレッドによって割り当てられたすべてのメモリをリークしている可能性があります(malloc、newなど)。そのため、プロセスは存続しますが、各障害/回復のたびにメモリがリークします。繰り返しになりますが、Apacheがメモリプールを使用するように、これを最小限に抑える方法がある程度あります。ただし、これは、スレッドが使用している可能性のあるサードパーティのライブラリによって割り当てられた可能性のあるメモリを保護しません。
また、一部の人々が指摘しているように、同期プリミティブを理解することは、おそらく実際に正しく行うのが最も難しいことです。この問題それ自体-すべてのコードに対して一般的なロジックを正しく取得するだけで-は大きな頭痛の種になります。不思議なデッドロックは最も奇妙な時期に発生する傾向があり、場合によってはプログラムが運用環境で実行されるまでも発生しないため、デバッグがさらに困難になります。これに加えて、同期プリミティブはプラットフォーム(WindowsとPOSIX)で大きく異なることが多く、デバッグがより困難になることが多く、いつでも競合状態(起動/初期化、ランタイム、シャットダウン)が発生する可能性があります。スレッドを使用したプログラミングは、初心者にはほとんど容赦がありません。そして、専門家でさえ、スレッド化自体の知識が一般的に複雑さを最小限に抑えるわけではないという理由だけで、容赦はほとんどありません。スレッド化されたコードの各行は、プログラムの全体的な複雑さを指数関数的に複雑にし、隠されたデッドロックまたは奇妙な競合状態がいつでも表面化する確率を高めることがあります。また、テストケースを作成してこれらの問題を解決することは非常に困難です。
これが、ApacheやPostgreSQLなどのプロジェクトの大部分がプロセスベースである理由です。PostgreSQLは、すべてのバックエンドスレッドを個別のプロセスで実行します。もちろん、これはまだ同期と競合状態の問題を軽減しませんが、かなりの保護を追加し、いくつかの点で物事を簡素化します。
それぞれが単一の実行スレッドを実行する複数のプロセスは、単一のプロセスで実行される複数のスレッドよりもはるかに優れている場合があります。また、AMQP(RabbitMQ、Qpidなど)やZeroMQなどの新しいピアツーピアコードの出現により、さまざまなプロセススペースやマシンやネットワークにスレッドを分割することが非常に簡単になり、物事が大幅に簡素化されます。それでも、それは特効薬ではありません。まだ対処する複雑さがあります。変数の一部をプロセス空間からネットワークに移動するだけです。
一番下の行は、スレッドのドメインに入る決定は軽いものではないということです。その領域に踏み込むと、ほぼ瞬時にすべてがより複雑になり、まったく新しい種類の問題があなたの人生に入ります。それは楽しくてかっこいいかもしれませんが、それは原子力のようなものです-物事がうまくいかないとき、彼らはひどく速く行くことができます。私は何年も前に臨界訓練のクラスに参加したことを覚えており、彼らはロスアラモスで第二次世界大戦の研究室でプルトニウムで遊んだ科学者の写真を見せました。多くの人が被ばくに対する予防策をほとんどまたはまったく講じておらず、瞬きするだけで、1回の明るく痛みのない閃光で、すべてが終わりました。数日後、彼らは死んでいた。リチャード・ファインマンは後にこれを「ドラゴンの尾をくすぐる」と呼んだそれは、スレッドで遊ぶことのようなものです(少なくとも私にとっては少なくとも)。最初はかなり無害に見えます。殺さないで
まず、シングルスレッドアプリケーションは、マルチコアCPUまたはハイパースレッディングを利用しません。ただし、シングルコアであっても、マルチスレッドを実行するシングルスレッドCPUには利点があります。
代替案と、それがあなたを幸せにするかどうかを検討してください。同時に実行する必要がある複数のタスクがあるとします。たとえば、2つの異なるシステムと通信し続ける必要があります。マルチスレッドなしでこれをどのように行いますか?おそらく独自のスケジューラを作成し、実行する必要のあるさまざまなタスクを呼び出せるようにします。これは、タスクを部分に分割する必要があることを意味します。おそらく、いくつかのリアルタイムの制約を満たす必要があります。部品が時間をかけすぎないようにする必要があります。そうしないと、他のタスクでタイマーが期限切れになります。これにより、タスクの分割がより困難になります。自分で管理する必要のあるタスクが多いほど、分割が必要になり、すべての制約を満たすためにスケジューラが複雑になります。
複数のスレッドがある場合、寿命はより簡単になります。プリエンプティブスケジューラは、いつでもスレッドを停止し、その状態を保持し、別のスレッドを再(開始)できます。スレッドが順番を回すと再起動します。利点:スケジューラーの記述の複雑さは既に行われているため、タスクを分割する必要はありません。また、スケジューラーは、ユーザー自身が認識していないプロセス/スレッドを管理できます。また、スレッドが何もする必要がない場合(何らかのイベントを待機している場合)、CPUサイクルを占有しません。これは、ダウンシングルスレッドスケジューラを作成する場合、それほど簡単に実行できません。(寝ることはそれほど難しくありませんが、どのように起きますか?)
マルチスレッド開発の欠点は、並行性の問題、ロック戦略などについて理解する必要があることです。エラーのないマルチスレッドコードの開発は非常に困難です。デバッグはさらに難しくなる可能性があります。
スレッドは速度だけでなく、並行性も重要です。
@Peterが提案したバッチアプリケーションではなく、WPFのようなGUIツールキットを使用している場合、1つのスレッドでユーザーやビジネスロジックとやり取りする方法を教えてください。
また、Webサーバーを構築しているとします。1つのスレッドだけで複数のユーザーに同時にサービスを提供する方法(他のプロセスは想定しない)
1つのスレッドだけでは不十分な多くのシナリオがあります。これが、50以上のコアと数百のスレッドを備えたIntel MICプロセッサなどの最近の進歩が行われている理由です。
はい、並列および並行プログラミングは困難です。しかし必要。
マルチスレッドにより、長時間の処理操作中にGUIインターフェースの応答性を維持できます。マルチスレッドを使用しないと、長いプロセスの実行中にユーザーがロックされたフォームを見続けることがあります。
他の入力(GUIまたは他の接続)に対する応答性を維持する必要があるIOのブロックを処理するアプリは、シングルスレッドにできません
IOライブラリにブロックせずにどれだけ読み込むことができるかを確認するメソッドを追加することはこれを助けることができますが、多くのライブラリはこれについて完全な保証をしません。
たくさんの良い答えがありますが、私がそうするようなフレーズは確かではありません-おそらくこれはそれを見るための別の方法を提供します:
スレッドは、オブジェクトやアクター、またはforループ(はい、if / gotoで実装できるループで実装するもの)のようなプログラミングの単純化です。
スレッドがなければ、状態エンジンを実装するだけです。私はこれを何度もしなければなりませんでした(初めてやったときは聞いたこともありませんでした。 "State"変数によって制御される大きなswitchステートメントを作成しただけです)。ステートマシンはまだ非常に一般的ですが、迷惑な場合があります。スレッドを使用すると、ボイラープレートの大きな部分がなくなります。
また、言語のランタイム実行をマルチCPUフレンドリーなチャンクに分割することを容易にします(アクターもそうです)。
Javaは、OSがスレッド化サポートを提供しないシステムで「グリーン」スレッドを提供します。この場合、これらは明らかにプログラミングの抽象化に過ぎないことがわかりやすくなります。
OSesは、タイムスライシングの概念を使用しており、各スレッドは実行する時間を取得してから、プリエンプトされます。そのようなアプローチは現在のスレッド化に取って代わることができますが、すべてのアプリケーションで独自のスケジューラーを書くのはやり過ぎです。さらに、I / Oデバイスなどで作業する必要があります。また、割り込みを発生させてスケジューラを実行できるように、ハードウェア側からのサポートが必要になります。基本的に、毎回新しいOSを作成することになります。
一般に、スレッドは、スレッドがI / Oを待機するか、スリープしている場合のパフォーマンスを改善できます。また、長いタスクを実行しながら、応答性の高いインターフェイスを作成し、プロセスを停止することができます。また、スレッド化により、真のマルチコアCPUの性能が向上します。
まず、スレッドは同時に2つ以上のことを実行できます(複数のコアがある場合)。複数のプロセスでこれを行うこともできますが、一部のタスクは複数のプロセスにうまく分散しません。
また、一部のタスクには、簡単に回避できないスペースが含まれています。たとえば、ディスク上のファイルからデータを読み取り、同時にプロセスに他の何かをさせることは困難です。タスクが必然的にディスクからの大量のデータの読み取りを必要とする場合、プロセスは何をしてもディスクを待機するのに多くの時間を費やします。
第二に、スレッドを使用すると、パフォーマンスが重要ではない大量のコードを最適化する必要がなくなります。スレッドが1つだけの場合、すべてのコードがパフォーマンスに重要です。ブロックされた場合、あなたは沈没します-そのプロセスによって実行されるタスクは前進できません。スレッドの場合、ブロックはそのスレッドと他のスレッドが一緒に来て、そのプロセスで実行する必要のあるタスクで動作できることにのみ影響します。
良い例は、まれにしか実行されないエラー処理コードです。タスクが非常にまれなエラーに遭遇し、そのエラーを処理するコードがメモリにページングする必要があるとします。ディスクがビジーであり、プロセスにスレッドが1つしかない場合、そのエラーを処理するコードをメモリにロードできるようになるまで前進できません。これにより、バースト応答が発生する可能性があります。
別の例は、データベース検索を行う必要が非常にまれな場合です。データベースが応答するのを待つと、コードに大きな遅れが生じます。しかし、これらのルックアップを実行する必要があるのは非常にまれなので、このコードをすべて非同期にする手間をかけたくありません。この作業を行うスレッドを使用すると、両方の長所を活用できます。この作業を行うスレッドにより、本来のパフォーマンスが重要ではなくなります。