ParallelOptions.MaxDegreeOfParallelism 是否全局应用于多个并发并行调用?

Is ParallelOptions.MaxDegreeOfParallelism applied globally over multiple concurrent Parallel calls?

在具有 32 个内核的 CPU 上考虑此代码 运行:

ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 8;

Parallel.For(0, 4, po, (i) =>
   {
      Parallel.For(0, 4, po, (j) =>
         {
            WorkMethod(i, j);  // assume a long-running method
         });
   }
);

我的问题是 WorkMethod(i, j) 的实际最大可能并发数是多少?是 4、8 还是 16?

ParallelOptions.MaxDegreeOfParallelism 未全局应用。如果您有足够的核心,并且调度程序认为合适,您将获得嵌套 MPD 值的乘积,每个 For 能够启动那么多任务(如果工作负载不受约束)。

考虑这个例子,3个任务可以再启动3个任务。这受限于 3.

MDP 选项
int k = 0;
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 3;

Parallel.For(0, 10, po, (i) =>
{
   Parallel.For(0, 10, po, (j) =>
         {
            Interlocked.Increment(ref k);
            Console.WriteLine(k);
            Thread.Sleep(2000);               
            Interlocked.Decrement(ref k);
         });
   Thread.Sleep(2000);
});

输出

1
2
3
4
7
5
6
8
9
9
5
6
7
9
9
8
8
9
...

如果 MDP 是全局的,我猜你只会得到 3,因为它不是你得到 9s。

ParallelOptions.MaxDegreeOfParallelism 不是全局的,它是每个并行循环。更具体地说,它设置了可以 运行 并行的最大任务数,而不是 运行 这些任务并行的最大内核或线程数。

一些演示测试

注意:我有4核8线程

代码中发生了什么

  • 我们运行正在使用 2 个异步方法;每个都启动嵌套的并行循环。
  • 我们将最大并行度设置为 2,睡眠时间设置为 2 秒,以模拟每个任务所做的工作
  • 因此,由于将 MaxDegreeOfParallelism 设置为 2,我们预计在 40 个任务完成之前最多可以达到 12 个并发任务(我只计算嵌套并行循环启动的任务)
    • 我如何获得 12?
      • 2 个最大并发任务在外循环中启动
      • +4 个来自内循环的最大并发任务(每个在外循环中启动的任务启动 2 个)
      • 那是 6(每个异步任务在 Main 中启动)
      • 共 12 个

测试码

using System;
using System.Threading;
using System.Threading.Tasks;

namespace forfun
{
    class Program
    {
        static void Main(string[] args)
        {
            var taskRunner = new TaskRunner();
            taskRunner.RunTheseTasks();
            taskRunner.RunTheseTasksToo();
            Console.ReadLine();
        }

        private class TaskRunner
        {
            private int _totalTasks = 0;
            private int _runningTasks = 0;

            public async void RunTheseTasks()
            {
                await Task.Run(() => ProcessThingsInParallel());
            }

            public async void RunTheseTasksToo()
            {
                await Task.Run(() => ProcessThingsInParallel());
            }

            private void ProcessThingsInParallel()
            {
                ParallelOptions po = new ParallelOptions();
                po.MaxDegreeOfParallelism = 2;
                Parallel.For(0, 4, po, (i) =>
                    {
                        Interlocked.Increment(ref _totalTasks);
                        Interlocked.Increment(ref _runningTasks);
                        Console.WriteLine($"{_runningTasks} currently running of {_totalTasks} total tasks");

                        Parallel.For(0, 4, po, (j) =>
                        {
                            Interlocked.Increment(ref _totalTasks);
                            Interlocked.Increment(ref _runningTasks);
                            Console.WriteLine($"{_runningTasks} currently running of {_totalTasks} total tasks");
                            WorkMethod(i, j);  // assume a long-running method
                            Interlocked.Decrement(ref _runningTasks);
                        });

                        Interlocked.Decrement(ref _runningTasks);
                    }
                );
            }

            private static void WorkMethod(int i, int l)
            {
                Thread.Sleep(2000);
            }
        }
    }
}

剧透,输出显示设置 MaxDegreeOfParallelism 不是全局的,不限于核心或线程数,并且专门为并发 运行ning 任务设置最大值。

最大设置为 2 的输出:

1 currently running of 1 total tasks
3 currently running of 3 total tasks
2 currently running of 2 total tasks
4 currently running of 4 total tasks
5 currently running of 5 total tasks
7 currently running of 7 total tasks

[ ... snip ...]

11 currently running of 33 total tasks
12 currently running of 34 total tasks
11 currently running of 35 total tasks
12 currently running of 36 total tasks
11 currently running of 37 total tasks
12 currently running of 38 total tasks
11 currently running of 39 total tasks
12 currently running of 40 total tasks

(输出会有所不同,但每次最大并发数应为 12)

没有最大设置的输出:

1 currently running of 1 total tasks
3 currently running of 3 total tasks
4 currently running of 4 total tasks
2 currently running of 2 total tasks
5 currently running of 5 total tasks
7 currently running of 7 total tasks

[ ... snip ...]

19 currently running of 28 total tasks
19 currently running of 29 total tasks
18 currently running of 30 total tasks
13 currently running of 31 total tasks
13 currently running of 32 total tasks
16 currently running of 35 total tasks
16 currently running of 36 total tasks
14 currently running of 33 total tasks
15 currently running of 34 total tasks
15 currently running of 37 total tasks
16 currently running of 38 total tasks
16 currently running of 39 total tasks
17 currently running of 40 total tasks

注意如何在不设置最大值的情况下获得最多 19 个并发任务 - 现在 2 秒睡眠时间限制了 可以在其他人完成之前开始的任务数量

将睡眠时间增加到 12 秒后的输出

1 currently running of 1 total tasks
2 currently running of 2 total tasks
3 currently running of 3 total tasks
4 currently running of 4 total tasks

[ ... snip ...]

26 currently running of 34 total tasks
26 currently running of 35 total tasks
27 currently running of 36 total tasks
28 currently running of 37 total tasks
28 currently running of 38 total tasks
28 currently running of 39 total tasks
28 currently running of 40 total tasks

获得最多 28 个并发任务

现在将循环设置为 10 嵌套在 10 中并将睡眠时间设置回 2 秒 - 同样没有最大设置

1 currently running of 1 total tasks
3 currently running of 3 total tasks
2 currently running of 2 total tasks
4 currently running of 4 total tasks

[ ... snip ...]

38 currently running of 176 total tasks
38 currently running of 177 total tasks
38 currently running of 178 total tasks
37 currently running of 179 total tasks
38 currently running of 180 total tasks
38 currently running of 181 total tasks

[ ... snip ...]

35 currently running of 216 total tasks
35 currently running of 217 total tasks
32 currently running of 218 total tasks
32 currently running of 219 total tasks
33 currently running of 220 total tasks

在 220 个任务全部完成之前最多获得 38 个并发任务

更多相关信息

ParallelOptions.MaxDegreeOfParallelism Property

The MaxDegreeOfParallelism property affects the number of concurrent operations run by Parallel method calls that are passed this ParallelOptions 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 and ForEach will utilize however many threads the underlying scheduler provides, so changing MaxDegreeOfParallelism from the default only limits how many concurrent tasks will be used.

  • 要获得最大并行度,不要设置它,而是让 TPL 及其调度程序处理它

  • 设置最大并行度只影响并发任务数,不影响使用的线程数

  • 并发任务的最大数量不等于可用线程的数量——线程仍然可以兼顾多个任务;即使您的应用程序正在使用所有线程,它仍然与机器托管的其他进程共享这些线程

Environment.ProcessorCount

Gets the number of processors on the current machine.

如果我们说 MaxDegreeOfParallelism = Environment.ProcessorCount 会怎样?

即使将最大并行度设置为 Environment.ProcessorCount 也不能动态地确保您获得最大并发性,无论您的应用程序 运行 宁在哪个系统上。这样做仍然限制了并行度,因为任何给定的线程都可以在许多任务之间切换——所以这只会将并发任务的数量限制为等于可用线程的数量——这并不一定意味着每个并发任务都会以一对一的关系整齐地分配给每个线程。