C#でスレッドプールを使用する場合 [閉まっている]


127

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

回答:


47

一定の処理を必要とする多数の論理タスクがあり、それを並行して実行する場合は、プール+スケジューラーを使用します。

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

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

通常、プールはプロセッサごとに2つのスレッドで構成されます(現在では4つ)が、必要なスレッド数がわかっていれば、必要な数のスレッドを設定できます。

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


2
わかりましたが、なぜこれがあなたがそれに取り組む方法なのかを説明できますか?たとえば、スレッドプールを使用してリモートサーバーからダウンロードしたり、ディスクIOを実行したりすることの欠点は何ですか?

8
スレッドが同期オブジェクト(イベント、セマフォ、ミューテックスなど)で待機している場合、スレッドはCPUを消費しません。
ブラノン

7
ブラノンが言ったように、一般的な神話は、複数のスレッドの作成はパフォーマンスに影響を与えるということです。実際、未使用のスレッドが消費するリソースはごくわずかです。コンテキストスイッチは、非常に需要の高いサーバーでのみ問題になり始めます(この場合、代替案については、I / O完了ポートを参照してください)。
FDCastel 2008年

12
アイドルスレッドはパフォーマンスに影響しますか?それは彼らが待つ方法に依存します。適切に記述され、同期オブジェクトを待機している場合、CPUリソースは消費されません。結果を確認するために定期的に起動するループで待機している場合、CPUを浪費しています。いつものように、それは良いコーディングに帰着します。
ビル、

2
アイドルマネージスレッドは、スタックのメモリを消費します。デフォルトでは、スレッドごとに1 MiBです。そのため、すべてのスレッドを機能させることをお勧めします。
Vadym Stetsiak、

48

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

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

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

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


28

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

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


8
リンクの場合は-1。私はそれが良いリンクだと確信していますが、私はSOが自給自足であると期待しています。
ジョンデイビス、

26
@ stimpy77-それはそれで間違った期待です。SOは、すべての質問に対する最終的な権限ではなく、各トピックに関するすべての詳細情報も、そのトピックに関係するすべてのSO回答で複製することができる(そしてすべき)ため、自給自足できません。(そして私はあなたがアウトバウンドリンクを持っているジョンスキートのすべての単一の回答だけでなく、アウトバウンドリンクを持っているすべてのSOユーザーからのすべての回答を否定するだけの十分な評判がないと思います:
Franci Penov

2
多分私は過度に簡潔でした、おそらく私は明確にする必要があります。私はリンクに反対ではありません。リンクのみを含む回答には反対です。それは答えではないと思います。ここで、リンクされたコンテンツがどのように適用されるかを要約するために、回答の短い要約が投稿されていたとしても、それは許容されます。その上、私は同じ問題の答えを探してここに来ましたが、この答えは私を苛立たせました。特定の問題に関してそれが何を言っているのかについて何かを知るためにクリックしなければならなかったので、この答えは私を苛立たせました。とにかく、ジョンスキートはこれとどこで関係していますか?そして、なぜ私は気にする必要がありますか?
ジョンデイビス

8
「投稿から2年後にあなたはこの投稿に来たので、私がここにコピーしたものはすべてもう時代遅れになっている可能性があります。」リンクかもしれません。リンクを投稿するときは、簡潔だが完全な要約を投稿してください。リンクが古くなったり、死んだりするかどうかはわかりません。
ジョンデイビス

2
私はstimpyに同意しません:実行不可能なために大量の情報を含む投稿の考えも、これについて誰かを呼び出すこともありません。ただし、リンクが操作不能になる可能性は、コンテンツが非推奨/廃止になるよりも多いと思います。したがって、機会が許せば、より多くのコンテンツがいいです。私たちはすべて(主に)ボランティアです。あなたが得たものに感謝してください
-Franciに

14

この無料の電子書籍を読むことを強くお勧めします 。JosephAlbahariによるThreading in C#

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

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

  • タスク並列ライブラリ(.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • 非同期デリゲート
  • BackgroundWorker

この電子書籍は、これらすべてを説明し、それらを使用する場合と独自のスレッドを作成する場合をアドバイスします。


8

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

ここで、これらの各コンポーネントがアイテムをスレッドプールにキューイングする場合、コンテキスト切り替えのオーバーヘッドが大幅に減少します。

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

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

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

注:スレッドプールスレッドを使用してデータをダウンロードしたり、ディスクI / Oを実行したりすることについて質問しました。これにはスレッドプールスレッドを使用しないでください(上記で概要を説明した理由によります)。代わりに非同期I / O(別名BeginXXおよびEndXXメソッド)を使用します。以下のためにFileStreamそれだろうBeginReadEndRead。用HttpWebRequestされることBeginGetResponseEndGetResponse。使い方はもっと複雑ですが、マルチスレッドI / Oを実行する適切な方法です。


1
ThreadPoolは賢い自動化です。「キューが0.5秒以上静止したままの場合、スレッドプールの容量まで、0.5秒ごとに1つずつ、より多くのスレッドを作成して応答します」(albahari.com/threading/#_Optimizing_the_Thread_Pool)。また、BeginXXX-EndXXXによるほぼ非同期の操作がThreadPoolを介して使用されます。したがって、データのダウンロードにはThreadPoolを使用するのが普通であり、暗黙的に使用されることがよくあります。
Artru

6

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


2
私たちはこれを難しい方法で発見しました!ASP.NetはThreadpoolを使用しているように見えるので、それを積極的に使用することはできませんでした。
noocyte-2008年

3

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

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


2

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

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

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


「スレッドプール」または「スレッドプール」を意味するかどうかを明確にしてください。これらは非常に異なるものです(少なくともMS CLRでは)。
bzlm 2008年

2

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

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

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

少しリアリズムを取りましょう:

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

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

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

実行ユニットを作成する最も簡単な方法は、タスクを使用することでしょう。タスクの優れた点は、タスクを作成してコード内でインラインで開始できることです(ただし、注意が必要な場合があります)。タスクをキャンセルするときに処理するキャンセルトークンを渡すことができます。また、promiseアプローチを使用してイベントを連鎖させ、特定のタイプの値を返すようにすることができます。さらに、非同期と待機により、より多くのオプションが存在し、コードの移植性が向上します。

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

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

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

試験結果


1
参考までに、タスクと.NETスレッドは.NET内で同時実行がシミュレートされ、管理はOSではなく.NET内で実行されることを言及するのを忘れました。後者は、同時実行の管理ではるかに効率的です。私は多くのことにタスクを使用しますが、重い実行パフォーマンスのためにOSスレッドを使用します。MSは、タスクと.NETスレッドの方が優れていると主張していますが、一般的には、.NETアプリ間の同時実行性のバランスをとるためです。ただし、サーバーアプリは、OSが並行処理を処理できるようにすることで最もパフォーマンスが向上します。

あなたのカスタムスレッドプールの実装を見たいです。素敵な書き込み!
フランシス

テスト結果がわかりません。das "Units Ran"はどういう意味ですか?あなたは512のスレッドと34のタクを比較しますか?説明していただけますか?
Elmue

ユニットは、タスク、スレッド、または.NET ThreadPoolワーカースレッドで同時に実行するための単なるメソッドです。私のテストでは、起動/実行パフォーマンスを比較しています。各テストには、512スレッドを最初から生成する30秒、512タスク、512 ThreadPoolワーカースレッド、またはコンテキストの実行を待機している512開始スレッドのプールの再開があります。TasksとThreadPoolワーカースレッドのスピンアップは遅いため、30秒ではそれらをすべてスピンアップするのに十分な時間ではありません。ただし、最初にThreadPool最小ワーカースレッド数を512に設定すると、タスクとThreadPoolワーカースレッドの両方が最初から512スレッドとほぼ同じ速度でスピンアップします。


1

スレッドプールは、使用可能なスレッドよりも多くのタスクを処理する場合に最適です。

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

MSDNのこのページを確認しください:http : //msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx


これは他の質問と関係があると思います。ある時点で使用可能なスレッドの数をどのようにして知ることができますか?

まあ、それは言うのは難しいです。パフォーマンステストを行う必要があります。ある時点でスレッドを追加しても速度は向上しません。マシンに搭載されているプロセッサの数を調べてください。これが出発点として最適です。次に、そこから上に行き、処理速度が改善されない場合は、スレッドを追加しないでください。
lajos 2008

1

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


1

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

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


1

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

私は多くの状況で私は重労働なしで私が欲しいものだけを与えてくれます。

乾杯。


実行し続けるシンプルなアプリで、他に1つのタスクがある場合は、このコードを実行するのは非常に簡単です。ただし、リンクは提供していません。仕様チュートリアル
zanlok

0

私は通常、別のスレッドで何かを実行する必要があるときはいつでもThreadpoolを使用しますが、実行または終了するタイミングを気にしません。ログのようなもの、またはファイルをバックグラウンドでダウンロードしているようなもの(非同期スタイルを実行するためのより良い方法があります)。さらに制御が必要な場合は、自分のスレッドを使用します。また、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();
        }
    }
}

このアプローチのいくつかの問題:-DequeueSafe()の呼び出しは、アイテムがEnqueuedSafe()になるまで待機します。タイムアウトを指定するMonitor.Wait()オーバーロードの1つを使用することを検討してください。-これをロックすることはベストプラクティスに従っておらず、読み取り専用オブジェクトフィールドを作成します。-Monitor.Pulse()は軽量ですが、キューに1つのアイテムしか含まれていないときに呼び出すと、より効率的です。-DeEnqueueUnblock()は、queue.Count> 0を優先的にチェックする必要があります(Monitor.PulseAllまたは待機タイムアウトが使用される場合に必要)
Craig Nicholson

0

スレッドプールがレイテンシをできるだけ最小限に抑えてコア全体に作業を分散できるようにしたかったので、他のアプリケーションとうまく連携する必要はありませんでした。.NETスレッドプールのパフォーマンスは、それよりも低いことがわかりました。コアごとに1つのスレッドが必要であることを知っていたので、独自のスレッドプール代替クラスを作成しました。このコードは、こちらの StackOverflowに関する別の質問への回答として提供されています

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.