【问题标题】:C# - Parallel Foreach slow creation of the threadsC# - 并行 Foreach 缓慢创建线程
【发布时间】:2017-03-02 21:07:18
【问题描述】:

我有一个 IEnumerable,其中包含许多需要并行处理的项目。这些项目不是 CPU 密集型的。 理想情况下,这些项目应该在 100 个或更多线程上同时执行。

我已尝试使用 Parallel.ForEach() 来完成此操作。这行得通,但问题是新线程的生成速度太慢。 Parallel.Foreach() 达到 100 个线程需要(太)长时间。我知道有一个 MaxDegreeOfParallelism 属性,但这是最大值,而不是最小值。

有没有办法在 100 个线程上立即执行 foreach? ThreadPool.SetMinThreads 是我们更愿意避免的,因为它对整个进程都有影响。

是否有使用自定义分区器的解决方案?

【问题讨论】:

  • 好吧,Parallel.ForEach 使用线程池,所以可以玩,但这是规则,所以SetMinThreads 似乎是唯一的选择(如果你想专门使用Parallel.ForEach)。
  • 您能否提供一个示例来展示您的处理 的含义。是受 IO 限制/受 CPU 限制,是多步骤进程吗……等等?
  • 但是,如果您的任务不是 CPU 密集型(因此 - 主要是 IO 密集型) - 您可以使用 async\await 结合 SemaphoreSlim 来限制并发,例如这里stackoverflow.com/a/10810730/5311735(但不要' t 像那里那样使用 Task.Run(await ...)。
  • 如果您的操作不是 CPU 密集型操作那么您一开始就不应该创建额外的线程。创建一堆线程只是让它们无所事事(因为您显然没有工作要做)将使您的代码更慢,并消耗更多资源,而不是更快。
  • 在 4 个内核上同时运行 100 个线程比一次使用 4 个线程需要更长的时间,因为在线程之间切换会涉及成本。 但是我建议你自己尝试一下,也许你确实有 100 多个内核。

标签: c# multithreading parallel-processing task-parallel-library


【解决方案1】:

我正在以 5 秒的超时时间 ping 很多设备。仅使用 4 个线程(4 核),您将如何尽可能快地做到这一点?

我将假设您在 LAN 上 ping 设备,并且每个设备都可以通过 IP 地址识别和访问。

namespace PingManyDevices {

    public class DeviceChecker {                

        public async Task<PingReply[]> CheckAllDevices(IEnumerable<IPAddress> devices) {
            var pings = devices.Select(address => new Ping().SendPingAsync(address, 5000));
            return await Task.WhenAll(pings);
        }
        /***
        * Maybe push it a little further
        ***/ 
        public async Task<PingReply[]> CheckAllDevices(IEnumerable<IPAddress> devices) {
            var pings = devices.AsParallel().Select(address => new Ping().SendPingAsync(address, 5000));
            return await Task.WhenAll(pings);
        }          
    }
} 

【讨论】:

    【解决方案2】:

    我已经成功使用 ThreadPool 而不是 Parallel:

    public static void ThreadForEach<T>(this IEnumerable<T> items, Action<T> action)
    {
        var mres = new List<ManualResetEvent>();
    
        foreach (var item in items)
        {
            var mre = new ManualResetEvent(false);
    
            ThreadPool.QueueUserWorkItem((i) =>
            {
                action((T)i);
                mre.Set();
            }, item);
    
            mres.Add(mre);
        }
    
        mres.ForEach(mre => mre.WaitOne());
    }
    

    在我不得不使用它的情况下,它的运行速度比使用 Parallel.ForEach 的尝试快。我只能推测这是因为它试图使用已经存在的线程(而不是承担创建新线程的开销)。

    【讨论】:

    • 您可以使用简单的 PLINQ 查询完全避免讨论线程和完全 foreach。但是,如果 OP 的处理以任何方式受 IO 限制,则需要转向同步解决方案。
    • Parallel 也使用线程池。并行没有坏。无论如何,您的代码有很多问题。首先,它使用List 而不是并发集合。其次,它完全是多余的。 await Task.WaitAll(items.Select(it=&gt;Task.Run(action(it)).ToArray()) 会做同样的事情
    • 最后,Parallel 用于 data 并行性。这意味着您要处理大量数据,而不是大量任务。通常,您不需要比处理每个数据分区的核心更多的任务。如果动作被阻止,那就是滥用Parallel
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-05
    • 1970-01-01
    相关资源
    最近更新 更多