web-dev-qa-db-ja.com

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

このコードを考えます:

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

1000のスレッドすべてがほぼ同時に生成されますか?

99
Jader Dias

いいえ、1000スレッドは開始されません-はい、使用されるスレッドの数を制限します。 Parallel Extensionsは、物理的に所有している数およびすでにビジーである数に基づいて、適切な数のコアを使用します。各コアに作業を割り当て、work stealingと呼ばれる手法を使用して、各スレッドが独自のキューを効率的に処理し、高価なクロス本当に必要なときにスレッドにアクセスします。

PFXチームブログ forloadsで、作業の割り当て方法やその他のあらゆるトピックに関する情報をご覧ください。

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

140
Jon Skeet

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

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

スレッドの同期のキャンセルと処理に関するドキュメントはあまり役に立ちません。 MSがMSDNでより良い例を提供できることを願っています。

忘れないでください、ボディコードは複数のスレッドで実行されるように書かれなければならず、通常のスレッド安全性の考慮事項すべてとともに、フレームワークはその要素を抽象化しません...まだ。

26

使用する「メンタルモデル」のアイデアについては、 反復ごとに1つのタスクを使用しますか? を参照してください。しかし、著者は「一日の終わりに、実装の詳細はいつでも変わる可能性があることを覚えておくことが重要です」と述べています。

5
Kevin Hakanson

プロセッサ/コアの数に基づいて最適な数のスレッドを算出します。すべて一度に生成されるわけではありません。

5
Colin Mackay

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

// 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)));
}
3