C#でスレッドプールを使用する場合[閉まっている]
-
02-07-2019 - |
質問
C#でマルチスレッドプログラミングを学習しようとしていますが、スレッドプールを使用するのが自分のスレッドを作成するのに最適なタイミングについて混乱しています。ある本では、スレッドプールを小さなタスクにのみ使用することを推奨しています(それが何を意味するにせよ)が、実際のガイドラインを見つけることができないようです。このプログラミングの決定を行う際に使用する考慮事項は何ですか?
解決
一定の処理を必要とする多くの論理タスクがあり、それを並行して実行する場合は、pool + schedulerを使用します。
リモートサーバーからのダウンロードやディスクアクセスなど、IO関連のタスクを同時に実行する必要があるが、これを数分に1回行う必要がある場合は、独自のスレッドを作成し、終了したらそれらを強制終了します。
編集:いくつかの考慮事項について、データベースアクセス、物理学/シミュレーション、AI(ゲーム)、および多くのユーザー定義タスクを処理する仮想マシンで実行されるスクリプトタスクにスレッドプールを使用します。
通常、プールはプロセッサごとに2つのスレッドで構成されます(最近では4つになる可能性が高い)が、必要なスレッド数がわかっている場合は、必要なスレッド数を設定できます。
編集:独自のスレッドを作成する理由は、コンテキストの変更によるものです(スレッドがプロセスとともにメモリをスワップする必要がある場合)。たとえば、スレッドを使用していないときに、無駄なコンテキスト変更を行うと、スレッドをそのまま放置すると、プログラムのパフォーマンスが半分になります(たとえば、3つのスリープスレッドと2つのアクティブスレッドがあります)。したがって、これらのダウンロードスレッドが待機している場合、CPUを大量に消費し、実際のアプリケーションのキャッシュを冷却しています
他のヒント
他の言語と同じ理由で、C#でスレッドプールを使用することをお勧めします。
実行中のスレッドの数を制限したい場合、またはスレッドの作成と破棄のオーバーヘッドが不要な場合は、スレッドプールを使用します。
小さなタスクとは、読む本は寿命の短いタスクを意味します。 1秒間だけ実行するスレッドを作成するのに10秒かかる場合、それはプールを使用する必要がある1つの場所です(私の実際の数字を無視して、それは重要な比率です)。
それ以外の場合は、単に目的の作業を行うのではなく、スレッドの作成と破棄に多くの時間を費やします。
.Netのスレッドプールの概要: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx
投稿には、スレッドプールを使用せず、代わりに独自のスレッドを開始する必要がある場合のいくつかのポイントもあります。
この無料の電子書籍を読むことを強くお勧めします。 Joseph AlbahariによるC#のスレッド
少なくとも「はじめに」を読んでください。セクション。この電子書籍は優れた入門書であり、高度なスレッド情報も豊富に含まれています。
スレッドプールを使用するかどうかを知ることはほんの始まりです。次に、ニーズに最適なスレッドプールの入力方法を決定する必要があります。
- タスク並列ライブラリ(.NET Framework 4.0)
- ThreadPool.QueueUserWorkItem
- 非同期デリゲート
- BackgroundWorker
この電子書籍ではこれらすべてについて説明し、いつ使用するか、独自のスレッドを作成するかをアドバイスしています。
スレッドプールは、スレッド間のコンテキスト切り替えを減らすように設計されています。いくつかのコンポーネントが実行されているプロセスを検討してください。これらの各コンポーネントは、ワーカースレッドを作成している可能性があります。プロセス内のスレッドが多いほど、コンテキストの切り替えに無駄な時間がかかります。
これらの各コンポーネントがスレッドプールにアイテムをキューイングしている場合、コンテキストスイッチングのオーバーヘッドははるかに少なくなります。
スレッドプールは、CPU(またはCPUコア)で実行される作業を最大化するように設計されています。そのため、デフォルトでは、スレッドプールはプロセッサごとに複数のスレッドを起動します。
スレッドプールを使用したくない状況がいくつかあります。 I / Oで待機している場合、またはイベントで待機している場合などは、そのスレッドプールスレッドを拘束し、他のユーザーが使用することはできません。長期実行タスクにも同じ考え方が当てはまりますが、長期実行タスクは主観的なものです。
Pax Diabloも良い点です。スレッドのスピンアップは無料ではありません。時間がかかり、スタックスペース用に追加のメモリを消費します。スレッドプールは、このコストを償却するためにスレッドを再利用します。
注:スレッドプールスレッドを使用してデータをダウンロードしたり、ディスクI / Oを実行したりするように依頼しました。これにはスレッドプールスレッドを使用しないでください(上記で説明した理由によります)。代わりに、非同期I / O(別名BeginXXおよびEndXXメソッド)を使用します。 BeginRead
および EndRead
である FileStream
の場合。 BeginGetResponse
および EndGetResponse
である HttpWebRequest
の場合。使用はより複雑ですが、マルチスレッドI / Oを実行する適切な方法です。
スレッドの枯渇が発生しやすいため、処理の重要な部分、可変部分、または不明な部分をブロックする可能性のある操作の.NETスレッドプールに注意してください。 .NET並列拡張を使用することを検討してください。これは、スレッド化された操作よりも多くの論理的抽象化を提供します。また、ThreadPoolを改善する新しいスケジューラも含まれています。 こちら
をご覧ください。小さなタスクにのみスレッドプールを使用する1つの理由は、スレッドプールスレッドの数が限られていることです。長い間使用されている場合は、そのスレッドが他のコードによって使用されるのを停止します。これが何度も発生すると、スレッドプールが使い果たされる可能性があります。
スレッドプールを使い果たすと微妙な影響が生じる可能性があります。たとえば、一部の.NETタイマーはスレッドプールスレッドを使用し、起動しません。
アプリケーションの存続期間全体など、長期間存続するバックグラウンドタスクがある場合は、独自のスレッドを作成するのが妥当です。スレッドで実行する必要がある短いジョブがある場合は、スレッドプーリングを使用します。
多数のスレッドを作成しているアプリケーションでは、スレッド作成のオーバーヘッドが大きくなります。スレッドプールを使用すると、スレッドが一度作成されて再利用されるため、スレッド作成のオーバーヘッドが回避されます。
私が取り組んだアプリケーションでは、スレッドの作成から短命のスレッド用のスレッドプールの使用に変更することで、アプリケーションのスループットが大幅に向上しました。
同時実行ユニットで最高のパフォーマンスを得るには、独自のスレッドプールを作成します。スレッドオブジェクトのプールは起動時に作成され、ブロッキング(以前は一時停止)になり、コンテキストの実行を待機します(標準のオブジェクトコードによって実装されたインターフェース)。
タスク対スレッド対.NET ThreadPoolに関する多くの記事では、パフォーマンスを決定するために必要なものを実際に提供できません。しかし、それらを比較すると、スレッド、特にスレッドのプールが勝ちます。 CPU全体に最適に分散され、起動が高速になります。
議論すべきことは、Windows(Windows 10を含む)のメイン実行ユニットがスレッドであり、OSコンテキストスイッチングのオーバーヘッドは通常無視できるという事実です。簡単に言えば、これらの記事の多くの説得力のある証拠を見つけることができませんでした。記事がコンテキスト切り替えを保存することでより高いパフォーマンスを主張するか、CPU使用率を向上させるかです。
これで少しリアリズムがわかりました:
私たちのほとんどは、アプリケーションを決定論的にする必要はありません。また、ほとんどの場合、オペレーティングシステムの開発に伴う、スレッドに関するハードノックの背景はありません。上に書いたのは初心者向けではありません。
したがって、最も重要なのは、プログラムしやすいものについて話し合うことです。
独自のスレッドプールを作成する場合、実行ステータスの追跡、サスペンドとレジュームのシミュレーション方法、および実行のキャンセル方法を考慮する必要があるため、やることが少しあります。 –アプリケーション全体のシャットダウンを含む。また、プールを動的に拡張するかどうかや、プールの容量制限についても考慮する必要があります。このようなフレームワークは1時間で作成できますが、それは何度も行ったことがあるからです。
おそらく実行ユニットを記述する最も簡単な方法は、タスクを使用することです。タスクの美しさは、コードをインラインで作成してキックオフできることです(ただし、注意が必要な場合があります)。タスクをキャンセルするときに処理するキャンセルトークンを渡すことができます。また、イベントをチェーンするためにプロミスアプローチを使用し、特定のタイプの値を返すようにすることができます。さらに、asyncとawaitを使用すると、より多くのオプションが存在し、コードの移植性が高まります。
本質的に、タスク対スレッド対.NET ThreadPoolの長所と短所を理解することが重要です。高いパフォーマンスが必要な場合は、スレッドを使用します。独自のプールを使用することを好みます。
比較する簡単な方法は、512個のスレッド、512個のタスク、および512個のThreadPoolスレッドを起動することです。 Threadsで最初に遅延が発生します(そのため、スレッドプールを記述する理由)が、タスクおよび.NET ThreadPoolスレッドがすべて開始するまでに数分かかる間、512スレッドすべてが数秒で実行されます。
そのようなテスト(16 GBのRAMを搭載したi5クアッドコア)の結果を以下に示します。実行に30秒かかります。実行されるコードは、SSDドライブで単純なファイルI / Oを実行します。
使用可能なスレッドよりも多くのタスクを処理する場合、スレッドプールは優れています。
すべてのタスクをスレッドプールに追加し、特定の時間に実行できるスレッドの最大数を指定できます。
MSDNのこちらページをご覧ください: http://msdn.microsoft.com/en-us /library/3dasc8as(VS.80).aspx
可能な場合は常にスレッドプールを使用し、可能な限り最高の抽象化レベルで作業します。スレッドプールはスレッドの作成と破壊を隠してくれます。これは通常良いことです!
ほとんどの場合、プールを使用できるのは、スレッドを作成する費用のかかるプロセスを避けるためです。
ただし、一部のシナリオでは、スレッドを作成する必要があります。たとえば、スレッドプールを使用しているのが自分だけではなく、作成したスレッドの寿命が長い場合(共有リソースの消費を避けるため)、またはスレッドのスタックサイズを制御する場合などです。
バックグラウンドワーカーを調査することを忘れないでください。
私は多くの状況で見つけますが、それは私がやりたいことだけを与えてくれます。
乾杯。
通常、別のスレッドで何かをする必要があるときは常にThreadpoolを使用しますが、実行時や終了時は気にしません。ロギングやファイルのバックグラウンドダウンロードなどのようなもの(非同期スタイルを実行するより良い方法はありますが)。もっと制御が必要なときは、自分のスレッドを使用します。また、「コマンドオブジェクト」を保存するために、スレッドセーフキュー(独自のキュー)を使用しています。 > 1スレッドで作業する必要がある複数のコマンドがある場合に便利です。したがって、Xmlファイルを分割し、各要素をキューに入れてから、これらの要素でいくつかの処理を実行する複数のスレッドを作成できます。 uni(VB.net!)でC#に変換したキューを作成しました。特別な理由なしに以下に含めました(このコードにはエラーが含まれている可能性があります)。
using System.Collections.Generic;
using System.Threading;
namespace ThreadSafeQueue {
public class ThreadSafeQueue<T> {
private Queue<T> _queue;
public ThreadSafeQueue() {
_queue = new Queue<T>();
}
public void EnqueueSafe(T item) {
lock ( this ) {
_queue.Enqueue(item);
if ( _queue.Count >= 1 )
Monitor.Pulse(this);
}
}
public T DequeueSafe() {
lock ( this ) {
while ( _queue.Count <= 0 )
Monitor.Wait(this);
return this.DeEnqueueUnblock();
}
}
private T DeEnqueueUnblock() {
return _queue.Dequeue();
}
}
}
できるだけ短いレイテンシでコア間で作業を分散するスレッドプールが必要でしたが、他のアプリケーションとうまく連携する必要はありませんでした。 .NETスレッドプールのパフォーマンスは、可能な限り良くないことがわかりました。コアごとに1つのスレッドが必要であることはわかっていたので、独自のスレッドプール代替クラスを作成しました。コードは、別のStackOverflowの質問への回答として提供されていますこちら。
元の質問に関して、スレッドプールは、反復計算を並列に実行できる部分に分割するのに役立ちます(結果を変更せずに並列で実行できると仮定した場合)。手動スレッド管理は、UIやIOなどのタスクに役立ちます。