web-dev-qa-db-ja.com

C#でスレッドプールを使用する場合

私はC#でマルチスレッドプログラミングを学ぼうとしており、スレッドプールを使用するのが自分のスレッドを作成するのがいつ最適かについて混乱しています。ある本では、スレッドプールを小さなタスク(それが何を意味するにせよ)にのみ使用することを推奨していますが、実際のガイドラインは見つかりません。このプログラミングを決定する際に使用する考慮事項は何ですか?

125

一定の処理を必要とする多くの論理タスクがあり、それを並行して実行したい場合は、pool + schedulerを使用します。

IOリモートサーバーからのダウンロードやディスクアクセスなどの関連タスクを同時に行う必要があるが、これを数分に1回行う必要がある場合は、独自のスレッドを作成して1回強制終了する終わった。

編集:いくつかの考慮事項について、データベースアクセス、物理学/シミュレーション、AI(ゲーム)、および多くのユーザー定義タスクを処理する仮想マシンで実行されるスクリプトタスクにスレッドプールを使用します。

通常、プールはプロセッサごとに2つのスレッドで構成されます(今日では4つになる可能性があります)が、必要なスレッド数がわかっている場合は、必要なスレッドの量を設定できます。

編集:独自のスレッドを作成する理由は、コンテキストの変更のためです(スレッドがプロセスと共にメモリをスワップする必要がある場合)。スレッドを使用していないときなど、無駄なコンテキストの変更があると、言うまでもなくそれらをそのままにしておくと、プログラムのパフォーマンスが簡単に半分になります(たとえば、3つのスリープスレッドと2つのアクティブスレッドがあります)。したがって、ダウンロードするスレッドが待機している場合、CPUを大量に消費し、実際のアプリケーションのキャッシュを冷却しています

46
Robert Gould

他の言語と同じ理由で、C#でスレッドプールを使用することをお勧めします。

実行中のスレッドの数を制限したい場合、またはスレッドの作成と破棄のオーバーヘッドが不要な場合は、スレッドプールを使用します。

小さなタスクとは、読む本は寿命の短いタスクを意味します。 1秒だけ実行するスレッドを作成するのに10秒かかる場合、それはプールを使用する必要がある1つの場所です(私の実際の数字を無視して、それが重要な比率です)。

それ以外の場合は、意図された作業を単純に行うのではなく、スレッドの作成と破棄に多くの時間を費やします。

48
paxdiablo

.Netのスレッドプールの概要は次のとおりです。 http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

投稿には、スレッドプールを使用せず、代わりに独自のスレッドを開始する必要がある場合のいくつかのポイントもあります。

28
Franci Penov

私はこの無料の電子書籍を読むことを強くお勧めします: Joseph AlbahariによるC#のスレッド化

少なくとも「はじめに」セクションを読んでください。この電子書籍は優れた紹介であり、高度なスレッド情報も豊富に含まれています。

スレッドプールを使用するかどうかを知ることはほんの始まりです。次に、スレッドプールへの入力方法がニーズに最も適しているかどうかを判断する必要があります。

  • タスク並列ライブラリ(.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • 非同期デリゲート
  • バックグラウンド

この電子書籍ではこれらすべてについて説明し、いつ使用するか、独自のスレッドを作成するかをアドバイスしています。

14
jrupe

スレッドプールは、スレッド間のコンテキスト切り替えを減らすように設計されています。いくつかのコンポーネントが実行されているプロセスを検討してください。これらの各コンポーネントは、ワーカースレッドを作成している可能性があります。プロセス内のスレッドが多いほど、コンテキストの切り替えに無駄な時間がかかります。

これらの各コンポーネントがスレッドプールにアイテムをキューイングしている場合、コンテキストスイッチングのオーバーヘッドははるかに少なくなります。

スレッドプールは、CPU(またはCPUコア)で実行される作業を最大化するように設計されています。そのため、デフォルトでは、スレッドプールはプロセッサごとに複数のスレッドを起動します。

スレッドプールを使用したくない状況がいくつかあります。 I/Oで待機している場合、またはイベントで待機している場合などは、そのスレッドプールスレッドを結び付け、他のユーザーが使用することはできません。長時間実行されるタスクには同じ考え方が当てはまりますが、長時間実行されるタスクは主観的なものです。

Pax Diabloも同様に良い点です。スレッドのスピンアップは無料ではありません。時間がかかり、スタックスペース用に追加のメモリを消費します。スレッドプールは、スレッドを再利用してこのコストを償却します。

注:スレッドプールスレッドを使用してデータをダウンロードしたり、ディスクI/Oを実行したりするように求めました。これにはスレッドプールスレッドを使用しないでください(上記で説明した理由によります)。代わりに、非同期I/O(別名BeginXXおよびEndXXメソッド)を使用します。 FileStreamの場合、BeginReadおよびEndReadになります。 HttpWebRequestの場合、BeginGetResponseおよびEndGetResponseになります。使用はより複雑ですが、マルチスレッドI/Oを実行する適切な方法です。

8
Brannon

スレッド不足に陥りやすいため、処理の重要な部分、可変部分、または不明な部分をブロックする可能性のある操作の.NETスレッドプールに注意してください。 .NET並列拡張を使用することを検討してください。これは、スレッド化された操作よりも多くの論理的抽象化を提供します。また、新しいスケジューラも含まれており、ThreadPoolを改善する必要があります。 こちら をご覧ください

6
mancaus

スレッドプールを小さなタスクにのみ使用する理由の1つは、スレッドプールスレッドの数が限られていることです。長い間使用されている場合、そのスレッドが他のコードによって使用されるのを停止します。これが何度も発生すると、スレッドプールが使い果たされる可能性があります。

スレッドプールを使い果たすと微妙な影響が生じる可能性があります。たとえば、一部の.NETタイマーはスレッドプールスレッドを使用し、起動しません。

3
Thomas Bratt

アプリケーションの存続期間全体など、長期間存続するバックグラウンドタスクがある場合は、独自のスレッドを作成するのが妥当です。スレッドで実行する必要がある短いジョブがある場合は、スレッドプーリングを使用します。

多くのスレッドを作成しているアプリケーションでは、スレッド作成のオーバーヘッドがかなり大きくなります。スレッドプールを使用すると、スレッドが1回作成されて再利用されるため、スレッド作成のオーバーヘッドが回避されます。

私が取り組んだアプリケーションでは、スレッドの作成から短命のスレッドにスレッドプールを使用するように変更することで、アプリケーションのスループットが本当に向上しました。

2
Bill

同時実行ユニットで最高のパフォーマンスを得るには、独自のスレッドプールを作成します。スレッドオブジェクトのプールは起動時に作成され、ブロッキング(以前は一時停止)に移行し、コンテキストの実行を待機します(実装される標準インターフェースを持つオブジェクトあなたのコード)。

タスク対スレッド対.NET ThreadPoolに関する多くの記事では、パフォーマンスを決定するために必要なものを実際に提供できません。しかし、それらを比較すると、スレッド、特にスレッドのプールが勝ちます。 CPU全体に最適に分散され、起動が高速になります。

議論すべきことは、Windows(Windows 10を含む)のメイン実行ユニットがスレッドであり、OSコンテキストスイッチングのオーバーヘッドは通常無視できるという事実です。簡単に言えば、これらの記事の多くが、コンテキストの切り替えを節約することでパフォーマンスが向上するか、CPU使用率が向上するかを説得する証拠を見つけることができませんでした。

少しリアリズムについて:

私たちのほとんどは、アプリケーションを決定論的にする必要はありません。また、ほとんどの場合、たとえばオペレーティングシステムの開発に伴うスレッドに関するハードノックの背景はありません。上に書いたのは初心者向けではありません。

したがって、最も重要なのは、プログラムするのが簡単なことについて話し合うことです。

独自のスレッドプールを作成する場合、実行ステータスの追跡、サスペンドと再開のシミュレーション方法、およびアプリケーション全体での実行のキャンセル方法に注意する必要があるため、やることが少しあります。シャットダウン。また、プールを動的に拡張するかどうかや、プールの容量制限についても考慮する必要があります。このようなフレームワークは1時間で作成できますが、それは何度もやったからです。

おそらく実行ユニットを記述する最も簡単な方法は、タスクを使用することです。タスクの美しさは、コードをインラインで作成してキックオフできることです(ただし、注意が必要な場合があります)。タスクをキャンセルするときに処理するキャンセルトークンを渡すことができます。また、イベントをチェーンするためにプロミスアプローチを使用し、特定のタイプの値を返すようにすることができます。さらに、asyncとawaitを使用すると、より多くのオプションが存在し、コードの移植性が向上します。

本質的に、タスク対スレッド対.NET ThreadPoolの長所と短所を理解することが重要です。高いパフォーマンスが必要な場合は、スレッドを使用します。独自のプールを使用することを好みます。

比較する簡単な方法は、512個のスレッド、512個のタスク、および512個のThreadPoolスレッドを起動することです。スレッドでは最初に遅延が発生します(そのため、スレッドプールを記述する理由)が、512スレッドはすべて数秒で実行されますが、タスクと.NET ThreadPoolスレッドはすべて開始するのに数分かかります。

以下は、このようなテスト(16 GBのRAMを搭載したi5クアッドコア)の結果であり、30秒ごとに実行されます。実行されるコードは、SSDドライブで単純なファイルI/Oを実行します。

テスト結果

2
user2769898

スレッドを作成する費用のかかるプロセスを回避するため、ほとんどの場合、プールを使用できます。

ただし、いくつかのシナリオでは、スレッドを作成する必要があります。たとえば、スレッドプールを使用しているのが自分だけではなく、作成したスレッドの寿命が長い場合(共有リソースの消費を避けるため)、またはスレッドのスタックサイズを制御する場合などです。

1
antonio

スレッドプールは、使用可能なスレッドよりも多くのタスクを処理する必要がある場合に役立ちます。

すべてのタスクをスレッドプールに追加し、特定の時間に実行できるスレッドの最大数を指定できます。

MSDNの this ページをご覧ください: http://msdn.Microsoft.com/en-us/library/3dasc8as(VS.80).aspx

1
lajos

可能な場合は常にスレッドプールを使用し、可能な限り最高の抽象化レベルで作業します。スレッドプールはスレッドの作成と破壊を隠します。これは通常良いことです!

1
JeffFoster

バックグラウンドワーカーを調査することを忘れないでください。

私は多くの状況で見つけます、それは私が重いものを持ち上げることなく私が望むものをちょうど与えます。

乾杯。

1
SetiSeeker

スレッドプールを使用して、可能な限り少ない遅延でコア間で作業を分散させたいと思っていましたが、他のアプリケーションとうまく連携する必要はありませんでした。 .NETスレッドプールのパフォーマンスは、可能な限り良くないことがわかりました。コアごとに1つのスレッドが必要であることはわかっていたので、独自のスレッドプール代替クラスを作成しました。コードは、別のStackOverflowの質問への回答として提供されます over here

元の質問に関して、スレッドプールは、反復計算を並列に実行できる部分に分割するのに役立ちます(結果を変更せずに並列で実行できると仮定した場合)。手動スレッド管理は、UIやIOなどのタスクに役立ちます。

0
cdiggins

私は通常、別のスレッドで何かをする必要があるときは常にスレッドプールを使用しますが、実行時や終了時は気にしません。ロギングのようなもの、あるいはファイルのバックグラウンドでのダウンロードも可能です(ただし、非同期スタイルを実行するより良い方法があります)。さらに制御が必要な場合は、独自のスレッドを使用します。また、スレッドセーフキュー(独自のハック)を使用して「コマンドオブジェクト」を保存することも、1スレッド以上で作業する必要がある複数のコマンドがある場合に便利です。したがって、Xmlファイルを分割し、各要素をキューに入れてから、これらの要素でいくつかの処理を実行する複数のスレッドを作成できます。私はC#に変換したuni(VB.net!)でこのようなキューを作成しました。以下に特別な理由なしに含めました(このコードにはエラーが含まれている可能性があります)。

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();
        }
    }
}
0
noocyte