Parallel.ForEachはアクティブなスレッドの数を制限しますか?


107

このコードを考えると:

var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

1000のスレッドすべてがほぼ同時にスポーンしますか?

回答:


149

いいえ、それは1000スレッドを開始しません-はい、使用されるスレッドの数を制限します。パラレル拡張機能を使用すると、物理的に持っているどのように多くに基づいて、コアの適切な数を使用して、既に忙しい何をしています。各コアに作業を割り当ててから、ワークスティーリングと呼ばれる手法を使用して、各スレッドが独自のキューを効率的に処理できるようにし、本当に必要な場合にのみ、高価なクロススレッドアクセスを実行します。

見ていPFXチームのブログのための負荷、それは仕事や他のトピックのすべての種類を割り当てる方法についての情報のを。

場合によっては、必要な並列度も指定できることに注意してください。


2
Parallel.ForEach(FilePathArray、path => ...を使用して今夜約24,000個のファイルを読み取り、読み取ったファイルごとに1つの新しいファイルを作成していました。非常に単純なコードです。6スレッドでも7200 RPMディスクを圧倒するのに十分であるようです私は100%の使用率で読み取りを行っていました。数時間の間に、Parallelライブラリが8,000を超えるスレッドをスピンオフするのを観察しました。MaxDegreeOfParallelismを使用してテストし、8000以上のスレッドが十分に表示されなくなったことを確認しました。結果
ジェイクドリュー

それは可能性があり、いくつかの縮退「doSomethingの」の1000個のスレッドを開始します。(現在、制限の設定に失敗し、200以上のスレッドを生成してSQL接続プールをポップしたプロダクションコードの問題に対処している場合と同様です。自明ではない作業には、最大DOPを設定することをお勧めします。明示的にCPUにバインドされていると
見なさ


28

シングルコアマシンでは、コレクションのParallel.ForEachパーティション(チャンク)はいくつかのスレッド間で機能しますが、その数は考慮に入れられたアルゴリズムに基づいて計算され、 ForEachに割り当てているスレッド。そのため、ForEachの本体部分が長時間実行のIOバインド/ブロッキング関数を呼び出し、スレッドが待機する場合、アルゴリズムはより多くのスレッドを生成し、それらの間でコレクションを再分割します。スレッドがすばやく完了し、たとえば、いくつかの数値を計算するなど、IOスレッドをブロックしない場合、アルゴリズムは、アルゴリズムがスループット(各反復の平均完了時間)に最適と見なされるポイントまでスレッド数を増加(または実際に減少)します

基本的に、すべてのさまざまなParallelライブラリ関数の背後にあるスレッドプールは、使用するスレッドの最適な数を計算します。物理プロセッサコアの数は、方程式の一部にすぎません。コアの数と生成されたスレッドの数の間に単純な1対1の関係はありません。

同期スレッドのキャンセルと処理に関するドキュメントはあまり役に立ちません。うまくいけば、MSはMSDNでより良い例を提供できるでしょう。

ボディコードは、複数のスレッドで実行するように作成する必要があり、通常のすべてのスレッドセーフティの考慮事項に加えて、フレームワークはその要因を抽象化しません...まだ。


1
「のForEachの身体の一部が長いの周りに待機しているスレッドを残して機能を遮断実行するに声をかけ..if、アルゴリズムは、より多くのスレッドを起動してくれます..」 - 多くのスレッドが作成したとして許さとして縮退のケースでは、この手段があるかもしれませんスレッドプールごと。
user2864740 2017

2
私が自分でデバッグしたとき、IOは+100スレッドを割り当てる可能性があります
FindOutIslamNow 2018

5

プロセッサー/コアの数に基づいて、最適なスレッド数を計算します。それらはすべて一度にスポーンするわけではありません。



4

すばらしい質問です。あなたの例では、クアッドコアプロセッサでも並列化のレベルはかなり低くなっていますが、待機していると並列化のレベルがかなり高くなる可能性があります。

// Max concurrency: 5
[Test]
public void Memory_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);
        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

次に、HTTPリクエストをシミュレートするために待機中の操作が追加されたときに何が起こるかを見てみましょう。

// Max concurrency: 34
[Test]
public void Waiting_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

私はまだ何も変更を加えておらず、並行性/並列化のレベルは劇的に跳ね上がりました。並行処理では、を使用して制限を増やすことができますParallelOptions.MaxDegreeOfParallelism

// Max concurrency: 43
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

// Max concurrency: 391
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(100000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

セッティングをお勧めしますParallelOptions.MaxDegreeOfParallelism。必ずしも使用中のスレッド数を増やすわけではありませんが、適切な数のスレッドのみを開始することが保証されます。これは懸念事項のようです。

最後に、あなたの質問に答えてください。いいえ、一度にすべてのスレッドを開始することはできません。競合状態のテストなど、完全に並行して呼び出す場合は、Parallel.Invokeを使用します。

// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[Test]
public void Test()
{
    ConcurrentBag<string> monitor = new ConcurrentBag<string>();
    ConcurrentBag<string> monitorOut = new ConcurrentBag<string>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(DateTime.UtcNow.Ticks.ToString());
        monitor.TryTake(out string result);
        monitorOut.Add(result);
    });

    var startTimes = monitorOut.OrderBy(x => x.ToString()).ToList();
    Console.WriteLine(string.Join(Environment.NewLine, startTimes.Take(10)));
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.