.NET 6 并行操作中 MaxDegreeOfParallelism = -1 的含义是什么?
ParallelOptions.MaxDegreeOfParallelism 的文档
属性指出:
MaxDegreeOfParallelism
属性会影响通过此ParallelOptions
实例传递的Parallel
方法调用运行的并发操作数。正的属性值将并发操作的数量限制为设定值。如果为-1,则并发运行的操作数没有限制。默认情况下,
For
和ForEach
将利用底层调度程序提供的线程数量,因此更改默认值的MaxDegreeOfParallelism
仅限制线程数量将使用并发任务。
我试图理解“无限制”在这种情况下的含义。根据以上文档摘录,我的期望是 Parallel.Invoke
配置了 MaxDegreeOfParallelism = -1
的操作将立即开始并行执行全部提供的操作
。但事实并非如此。这是一个包含 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);
输出:(
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。所以“无限制”并不意味着我认为的意思。我的问题是:MaxDegreeOfParallelism = -1
配置完全意味着什么,所有四个并行
方法(对于
, ForEach
、ForEachAsync
和 Invoke
)?我想详细了解这些方法以这种方式配置时的行为。如果 .NET 版本之间存在行为差异,我对当前的 .NET 版本 (.NET 6) 很感兴趣,该版本还引入了新的 Parallel.ForEachAsync
API。
第二个问题:MaxDegreeOfParallelism = -1 与在这些方法中省略可选的parallelOptions 参数是否完全相同?
说明:我对 使用默认的 TaskScheduler
配置。我对使用专门或自定义调度程序可能出现的任何复杂情况不感兴趣。
The documentation of the ParallelOptions.MaxDegreeOfParallelism
property states that:
The
MaxDegreeOfParallelism
property affects the number of concurrent operations run byParallel
method calls that are passed thisParallelOptions
instance. A positive property value limits the number of concurrent operations to the set value. If it is -1, there is no limit on the number of concurrently running operations.By default,
For
andForEach
will utilize however many threads the underlying scheduler provides, so changingMaxDegreeOfParallelism
from the default only limits how many concurrent tasks will be used.
I am trying to understand what "no limit" means in this context. Based on the above excerpt from the docs, my expectation was that a Parallel.Invoke
operation configured with MaxDegreeOfParallelism = -1
would start executing immediately in parallel all the supplied actions
. But this is not what happening. Here is an experiment with 12 actions:
int concurrency = 0;
Action action = new Action(() =>
{
var current = Interlocked.Increment(ref concurrency);
Console.WriteLine(@quot;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
The result of this experiment does not match my expectations. Not all actions were invoked immediately. The maximum concurrency recorded is 6, and sometimes 7, but not 12. So the "no limit" does not mean what I think it means. My question is: what does the MaxDegreeOfParallelism = -1
configuration means exactly, with all four Parallel
methods (For
, ForEach
, ForEachAsync
and Invoke
)? I want to know in details what's the behavior of these methods, when configured this way. In case there are behavioral differences between .NET versions, I am interested about the current .NET version (.NET 6), which also introduced the new Parallel.ForEachAsync
API.
Secondary question: Is the MaxDegreeOfParallelism = -1
exactly the same with omitting the optional parallelOptions
argument in these methods?
Clarification: I am interested about the behavior of the Parallel
methods when configured with the default TaskScheduler
. I am not interested about any complications that might arise by using specialized or custom schedulers.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
定义特意声明为
-1表示并发操作的数量不会被人为限制。
并且它并没有说所有操作都会立即开始。线程池管理器通常将可用线程的数量保持在核心数(或逻辑处理器的核心数的 2 倍),这被认为是最佳线程数(我认为这个数字是 [核心数/逻辑处理器 + 1]) 。这意味着当您开始执行操作时,立即开始工作的可用线程数就是这个数字。
线程池管理器定期运行(每秒两次),如果没有线程完成,则添加新线程(或者在线程太多时在相反的情况下删除)。
要了解这一点,一个很好的实验是快速连续运行两次实验。在第一个实例中,开始时的并发作业数应约为核心/逻辑处理器数 + 1,在第二次运行中,它应为运行的作业数(因为创建这些线程是为了服务第一次运行):
以下是代码的修改版本:
输出:
我的计算机有 4 个核心(8 个逻辑处理器),当作业在“冷”
TaskScheduler.Default
上运行时,首先会立即启动其中的 8+1 个作业之后定期添加新线程。然后,当“热”运行第二批时,所有作业都会同时启动。
Parallel.ForEachAsync
当使用 Parallel.ForEachAsync 运行类似的示例时,行为会有所不同。这项工作是在恒定的并行水平上完成的。请注意,这与线程无关,因为如果您等待 Task.Delay(因此不会阻塞线程),并行作业的数量将保持不变。
如果我们查看采用 ParallelOptions 的版本的源代码,它会将 parallelOptions.EffectiveMaxConcurrencyLevel 作为
dop
传递给执行实际工作的私有方法。如果我们进一步观察,我们可以看到:
最后看一眼,我们可以看到最终值是Environment.ProcessorCount。
这就是现在的情况,我不确定在 .NET 7 中是否会保持这样。
The definition is deliberately states as
-1 means that the number of number of concurrent operations will not be artificially limited.
and it doesn't say that all actions will start immediately.The thread pool manager normally keeps the number of available threads at the number of cores (or logical processor which are 2x number of cores) and this is considered the optimum number of threads (I think this number is [number of cores/logical processor + 1]) . This means that when you start executing your actions the number of available threads to immediately start work is this number.
Thread pool manager runs periodically (twice a second) and if none of the threads have completed a new one is added (or removed in the reverse situation when there are too many threads).
A good experiment to see this in action is to run your experiment twice in quick succession. In the first instance the number of concurrent jobs at the beginning should be around the number of cores/logical processor + 1 and in the 2nd run it should be the number of jobs run (because these threads were created to service the 1st run):
Here's a modified version of your code:
Output:
My computer has 4 cores (8 logical processors) and when the jobs run on a "cold"
TaskScheduler.Default
at first 8+1 of them are started immediately and after that a new thread is added periodically.Then, when running the second batch "hot" then all jobs start at the same time.
Parallel.ForEachAsync
When a similar example is run with
Parallel.ForEachAsync
the behaviour is different. The work is done at a constant level of paralellism. Please note that this is not about threads because if youawait Task.Delay
(so not block the thread) the number of parallel jobs stays the same.If we peek at the source code for the version taking
ParallelOptions
it passesparallelOptions.EffectiveMaxConcurrencyLevel
asdop
to the private method which does the real work.If we peek further we can see that:
DefaultDegreeOfParallelism
.One last peek, and we can see the final value is
Environment.ProcessorCount
.This is what it is now and I am not sure if this will stay like this in .NET 7.