【问题标题】:Is ParallelOptions.MaxDegreeOfParallelism applied globally over multiple concurrent Parallel calls?ParallelOptions.MaxDegreeOfParallelism 是否全局应用于多个并发并行调用?
【发布时间】:2019-08-23 21:12:04
【问题描述】:

假设这段代码在 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?

【问题讨论】:

  • 将使用 8 个核心。但这样做并不是一个好主意。见this
  • @CodingYoshi 我知道我的例子是不可移植的,考虑到不同客户端机器上的不同核心数量。那么你能确认ParallelOptions.MaxDegreeOfParallelism 确实是所有通过它的并发Parallel.* 调用的全局限制吗?

标签: c# task-parallel-library parallel.foreach


【解决方案1】:

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,因为你得到的不是 9。

【讨论】:

  • 我无法测试它,因为我只有 4 个内核可用。感谢您的反馈,因为它反驳了 CodingYoshi 的评论。但是,我需要有人明确回答这个问题。您是否真的在 8 核以上的机器上运行测试代码?
  • @SpecialSauce 这是在我早上喝咖啡之前,我再次更新了希望这次有意义。
  • 感谢您的测试;根据控制台输出,这个答案确实是确定的。它似乎不是全局的,并且确实可以乘法扩展。感谢您对其进行实际测试(与 CodingYoshi 不同)。
【解决方案2】:

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不是全局的,不限于核心或线程数,而是专门设置并发运行任务的最大值。

最大值设置为 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

MaxDegreeOfParallelism 属性会影响通过传递此 ParallelOptions 实例的 Parallel 方法调用运行的并发操作数。正属性值将并发操作数限制为设置值。如果为-1,则对并发运行的操作数没有限制。

默认情况下,无论底层调度程序提供多少线程,For 和 ForEach 都会使用,因此从默认值更改 MaxDegreeOfParallelism 只会限制将使用多少并发任务。

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

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

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

Environment.ProcessorCount

获取当前机器上的处理器数量。

如果我们说MaxDegreeOfParallelism = Environment.ProcessorCount呢?

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-10
    • 2020-03-30
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多