多线程 foreach 减慢主线程

Multithread foreach slows down main thread

编辑:根据评论中的讨论,我高估了多少线程会有所帮助,并以合理的 MaxDegreeOfParallelism 回到 Parallell.ForEach,只需要等待它出。

我有一个二维数组数据结构,并对数据切片执行操作。同时处理所有数据只需要大约 1000 个线程。基本上所有 ~7000 个数据点大约有 1000“天”的数据,我想在新线程中并行处理每一天的数据。

我的问题是在子线程中工作显着 减慢了主线程启动它们的时间。如果我没有在子线程中完成任何工作,主线程基本上会立即启动它们。在我下面的例子中,只需要一点点工作,就需要大约 65 毫秒来启动所有线程。在我的实际用例中,工作线程将花费大约 5-10 秒来计算它们需要的所有内容,但我希望它们全部立即启动,否则,我基本上 运行 按顺序执行工作。我不明白为什么他们的工作会减慢启动它们的主线程。

数据的设置方式无关紧要(我希望如此)。它的设置方式可能看起来很奇怪,我只是在模拟我如何接收数据。重要的是,如果您在 DoThreadWork 方法中注释掉 foreach 循环,则启动线程所需的时间会更短。

我有 for (var i = 0; i < 4; i++) 循环只是为了 运行 多次模拟以查看 4 组计时结果,以确保它不仅仅是第一次慢。

这是模拟我的真实代码的代码片段:

public static void Main(string[] args)
{
    var fakeData = Enumerable
        .Range(0, 7000)
        .Select(_ => Enumerable.Range(0, 400).ToArray())
        .ToArray();

    const int offset = 100;
    var dataIndices = Enumerable
        .Range(offset, 290)
        .ToArray();

    for (var i = 0; i < 4; i++)
    {
        var s = Stopwatch.StartNew();
        var threads = dataIndices
            .Select(n =>
            {
                var thread = new Thread(() =>
                {
                    foreach (var fake in fakeData)
                    {
                        var sliced = new ArraySegment<int>(fake, n - offset, n - (n - offset));
                        DoThreadWork(sliced);
                    }
                });

                return thread;
            })
            .ToList();

        foreach (var thread in threads)
        {
            thread.Start();
        }
        
        Console.WriteLine($"Before Join: {s.Elapsed.Milliseconds}");

        foreach (var thread in threads)
        {
            thread.Join();
        }
        
        Console.WriteLine($"After Join: {s.Elapsed.Milliseconds}");
    }
}

private static void DoThreadWork(ArraySegment<int> fakeData)
{
    // Commenting out this foreach loop will dramatically increase the speed
    // in which all the threads start
    var a = 0;
    foreach (var fake in fakeData)
    {
        // Simulate thread work
        a += fake;
    }
}

使用 thread/task 池并将 thread/task 计数限制为最多 2*(CPU Cores)。创建更多线程并不能神奇地完成更多工作,因为您需要 运行 个硬件“线程”(non-SMT CPU 的每个 CPU 核心 1 个,2每个内核用于 Intel HT,AMD 的 SMT 实现)。执行数百到数千个不必被动等待异步回调(即 I/O)的线程会使 运行 线程的效率大大降低,因为 CPU 与上下文切换不兼容原因。