【问题标题】:WithDegreeOfParallelism(N>CPU count)WithDegreeOfParallelism(N>CPU 计数)
【发布时间】:2013-10-28 15:13:08
【问题描述】:
System.Threading.ThreadPool.SetMaxThreads(50, 50);
File.ReadLines().AsParallel().WithDegreeOfParallelism(100).ForAll((s)->{
/*
some code which is waiting external API call 
and do not utilize CPU 
*/
});

我的系统中的线程数从未超过 CPU 数。 我可以使用 PLINQ 并让每个 CPU 获得多个线程吗?

【问题讨论】:

  • 这有什么意义?性能不会提高。
  • @GSerg:如果发生某种阻塞 IO,它可能会起作用。不过不推荐。
  • 如果我是你,我会调查 TPL DataFlow。 msdn.microsoft.com/en-us/library/hh228603.aspx。或者,如果您想使用纯 TPL,请查看 Parallel.For 和 Parallel.ForEach
  • @Vladimir 这就是阻塞IO的定义;你开始一个 IO 操作,然后让一个线程坐在那里什么都不做,等待它完成,而不是使用异步,这样你就可以在它完成时做一些事情,而无需线程等待操作。
  • @Vladimir:声明“我没有阻塞 IO”。直接与“每个任务只会向外部慢速 API 发送请求并等待约 3 秒响应”相矛盾。您正在阻塞 IO。约 3 秒。

标签: c# plinq


【解决方案1】:

如果您正在调用外部 Web API,您可能会达到并发同时连接数的限制,该限制设置为 2。在您的应用程序开始时,请执行以下操作:

System.Net.ServicePointManager.DefaultConnectionLimit = 4096;
System.Net.ServicePointManager.Expect100Continue = false;

试试看是否有帮助。如果没有,您尝试并行化的例程中可能存在其他瓶颈。

另外,正如其他响应者所说,ThreadPool 会根据负载决定启动多少线程。根据我使用 TPL 的经验,我发现线程数随时间增加:应用运行时间越长,负载越重,启动的线程就越多。

【讨论】:

  • 谢谢,但外部 API 不是这里的障碍
【解决方案2】:

PLINQ 使用爬山算法来确定 TPL 使用的线程池的最佳大小。我认为,如果您在任务中投入大量 I/O,那么看到线程数超过 CPU 数是令人愉快的。

也就是说,我从未见过比 CPU 计数更多的线程 :) 。但也许我从来没有遇到过正确的情况。

【讨论】:

  • AFAIK,PLINQ 不使用任何此类算法,ThreadPool 使用。
  • 这真是让人毛骨悚然,PLINQ 使用了 Pool。
  • qwerto:你从来没有遇到过错误的情况。等待 I/O 的线程很浪费。
  • 好吧,如果多线程可以有效降低 I/O 延迟成本,难道不值得交易吗?
  • @ChristopherStevenson:不,因为可以通过使用不会阻塞 IO 的正确异步 API 进一步减少它们。在阻塞代码中抛出额外的线程是穷人的替代方案来拥抱异步 IO。鉴于异步在移动设备上本质上是强制性的,而且 async/await 非常流畅,我认为没有任何理由不使用它。
【解决方案3】:

我使用以下代码对此进行了测试:

var lines = Enumerable.Range(0, 200).ToArray();
int currentThreads = 0;
int maxThreads = 0;
object l = new object();
lines.AsParallel().WithDegreeOfParallelism(100).ForAll(
    s =>
    {
        lock (l)
        {
            currentThreads++;
            if (currentThreads > maxThreads)
            {
                maxThreads = currentThreads;
                Console.WriteLine(maxThreads);
            }
        }
        Thread.Sleep(3000);
        lock (l)
        {
            currentThreads--;
        }
    });

Console.WriteLine();
Console.WriteLine(maxThreads);

基本上,它记录当前并发执行的迭代次数,然后保存遇到的最大值。

结果差异很大,在 15 到 25 之间,但它总是比我的计算机的 CPU 数量 (4) 多得多。增加睡眠时间会增加最大并发线程数。所以看起来这里的限制因素是ThreadPool:它会缓慢地创建新线程,特别是当作业完成相对较快时。

如果您想增加使用的线程数,您需要使用SetMinThreads()(而不是SetMaxThreads())。如果我将最小值设置为 50,实际使用的线程数在 60 左右。

但是拥有几十个只做等待的线程是非常低效的,尤其是在内存消耗方面。您应该考虑改用异步方法。

【讨论】:

    【解决方案4】:

    PLINQ 不适合这种情况。 我发现下一篇文章对我有用。 http://msdn.microsoft.com/en-us/library/hh228609(v=vs.110).aspx

    【讨论】:

    • 关于 TPL 数据流。但是如果你尝试将它与阻塞方法一起使用,我认为它的行为与 PLINQ 相同,因为它仍然使用ThreadPool
    • svick,我测试了两种方法:PLINQ 和带有 ActionBlock 的 TPL DataFlow。它们都显示我有 20 个线程正在运行,但 PLINQ 顺序执行这些任务,而 TPL DataFlow 以并行方式运行它们
    • 这没有任何意义。我怀疑你的代码有问题。
    【解决方案5】:

    简短回答:不。

    线程数量取决于 .Net Framework 运行时。没有开发人员可以控制 TPL(任务并行库)使用的线程数。

    编辑

    感谢其他一些反馈:实际上可以(但不推荐)手动控制 PLINQ 和 TPL 使用的 ThreadPool 中的线程数。

    我认为任何并行化问题都需要仔细考虑,仔细构建和测试。这其中有很多微妙之处。

    【讨论】:

    • 其实默认dispatcher就是ThreadPool的一个功能,是可以控制的。
    • 好吧,别让我挂了。怎么样?
    • 使用 ThreadPool.SetMinThreads 和 ThreadPool.SetMaxThreads 方法。但是,我相信如果您发现自己在使用这些,那么您的代码中的其他地方就会出现问题。
    猜你喜欢
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-09
    • 2010-10-18
    • 2012-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多