[英]What is the meaning of the MaxDegreeOfParallelism = -1 in Parallel operations in .NET 6?
ParallelOptions.MaxDegreeOfParallelism
屬性的文檔指出:
MaxDegreeOfParallelism
屬性影響通過此ParallelOptions
實例傳遞的Parallel
方法調用運行的並發操作數。 正的屬性值將並發操作數限制為設置值。 如果為-1,則表示並發運行的操作數沒有限制。默認情況下,
For
和ForEach
將使用底層調度程序提供的任意多個線程,因此從默認值更改MaxDegreeOfParallelism
僅限制將使用的並發任務數。
我試圖理解“無限制”在這種情況下的含義。 根據上述文檔摘錄,我的期望是配置有MaxDegreeOfParallelism = -1
的Parallel.Invoke
操作將立即開始並行執行所有提供的actions
。 但這不是正在發生的事情。 這是一個包含 12 個動作的實驗:
int concurrency = 0;
Action action = new Action(() =>
{
var current = Interlocked.Increment(ref concurrency);
Console.WriteLine(@$"Started an action at {DateTime
.Now:HH:mm:ss.fff} on thread #{Thread
.CurrentThread.ManagedThreadId} with concurrency {current}");
Thread.Sleep(1000);
Interlocked.Decrement(ref concurrency);
});
Action[] actions = Enumerable.Repeat(action, 12).ToArray();
var options = new ParallelOptions() { MaxDegreeOfParallelism = -1 };
Parallel.Invoke(options, actions);
Output:
Started an action at 11:04:42.636 on thread #6 with concurrency 4
Started an action at 11:04:42.636 on thread #7 with concurrency 5
Started an action at 11:04:42.629 on thread #1 with concurrency 1
Started an action at 11:04:42.636 on thread #8 with concurrency 3
Started an action at 11:04:42.630 on thread #4 with concurrency 2
Started an action at 11:04:43.629 on thread #9 with concurrency 6
Started an action at 11:04:43.648 on thread #6 with concurrency 6
Started an action at 11:04:43.648 on thread #8 with concurrency 6
Started an action at 11:04:43.648 on thread #4 with concurrency 6
Started an action at 11:04:43.648 on thread #7 with concurrency 6
Started an action at 11:04:43.648 on thread #1 with concurrency 6
Started an action at 11:04:44.629 on thread #9 with concurrency 6
(現場演示)
這個實驗的結果與我的預期不符。 並非所有操作都被立即調用。 記錄的最大並發是6,有時是7,但不是12。所以“不限制”並不是我想的意思。 我的問題是:對於所有四種Parallel
方法( For
、 ForEach
、 ForEachAsync
和Invoke
), MaxDegreeOfParallelism = -1
配置究竟意味着什么? 我想詳細了解以這種方式配置時這些方法的行為是什么。 如果 .NET 版本之間存在行為差異,我對當前的 .NET 版本 (.NET 6) 感興趣,該版本還引入了新的Parallel.ForEachAsync
API。
第二個問題: MaxDegreeOfParallelism = -1
是否與在這些方法中省略可選的parallelOptions
參數完全相同?
說明:我對使用默認TaskScheduler
配置時Parallel
方法的行為感興趣。 我對使用專門或自定義調度程序可能出現的任何復雜情況不感興趣。
定義中特意聲明為-1 means that the number of number of concurrent operations will not be artificially limited.
它並沒有說所有的動作都會立即開始。
線程池管理器通常將可用線程數保持在內核數(或邏輯處理器是內核數的 2 倍),這被認為是最佳線程數(我認為這個數字是 [內核數/邏輯處理器 + 1]) 。 這意味着當您開始執行您的操作時,立即開始工作的可用線程數就是這個數字。
線程池管理器定期運行(每秒兩次),如果沒有線程完成,則添加一個新線程(或者在線程太多時在相反的情況下刪除)。
一個很好的實驗是快速連續運行兩次實驗,以了解這一點。 在第一個實例中,開始時並發作業的數量應該大約是內核/邏輯處理器的數量 + 1,而在第二次運行中,它應該是運行的作業數量(因為創建這些線程是為了服務第一次運行):
這是您的代碼的修改版本:
using System.Diagnostics;
Stopwatch sw = Stopwatch.StartNew();
int concurrency = 0;
Action action = new Action(() =>
{
var current = Interlocked.Increment(ref concurrency);
Console.WriteLine(@$"Started at {sw.ElapsedMilliseconds} with concurrency {current}");
Thread.Sleep(10_000);
current = Interlocked.Decrement(ref concurrency);
});
Action[] actions = Enumerable.Repeat(action, 12).ToArray();
var options = new ParallelOptions() { MaxDegreeOfParallelism = -1 };
Parallel.Invoke(options, actions);
Parallel.Invoke(options, actions);
Output:
Started at 114 with concurrency 8
Started at 114 with concurrency 1
Started at 114 with concurrency 2
Started at 114 with concurrency 3
Started at 114 with concurrency 4
Started at 114 with concurrency 6
Started at 114 with concurrency 5
Started at 114 with concurrency 7
Started at 114 with concurrency 9
Started at 1100 with concurrency 10
Started at 2097 with concurrency 11
Started at 3100 with concurrency 12
Started at 13110 with concurrency 1
Started at 13110 with concurrency 2
Started at 13110 with concurrency 3
Started at 13110 with concurrency 5
Started at 13110 with concurrency 7
Started at 13110 with concurrency 9
Started at 13110 with concurrency 10
Started at 13110 with concurrency 11
Started at 13110 with concurrency 4
Started at 13110 with concurrency 12
Started at 13110 with concurrency 6
Started at 13110 with concurrency 8
我的計算機有 4 個內核(8 個邏輯處理器),當作業在“冷” TaskScheduler.Default
上運行時。默認首先立即啟動其中的 8+1 個,然后定期添加一個新線程。
然后,當運行第二批“熱”時,所有作業同時開始。
當使用Parallel.ForEachAsync
運行類似示例時,行為會有所不同。 這項工作是在恆定的並行水平下完成的。 請注意,這與線程無關,因為如果您await Task.Delay
(因此不阻塞線程),並行作業的數量將保持不變。
如果我們查看采用ParallelOptions
的版本的源代碼,它會將parallelOptions.EffectiveMaxConcurrencyLevel
作為dop
傳遞給執行實際工作的私有方法。
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source!!, ParallelOptions parallelOptions!!, Func<TSource, CancellationToken, ValueTask> body!!)
{
return ForEachAsync(source, parallelOptions.EffectiveMaxConcurrencyLevel, ...);
}
如果我們進一步觀察,我們可以看到:
DefaultDegreeOfParallelism
。/// <param name="dop">A integer indicating how many operations to allow to run in parallel.</param>
(...)
private static Task ForEachAsync<TSource>(IEnumerable<TSource> source, int dop,
{
...
if (dop < 0)
{
dop = DefaultDegreeOfParallelism;
}
最后一瞥,我們可以看到最終值為Environment.ProcessorCount
。
private static int DefaultDegreeOfParallelism => Environment.ProcessorCount;
這就是現在的樣子,我不確定在 .NET 7 中是否會保持這樣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.