【问题标题】:How often does Parallel.For invoke localInit?Parallel.For 多久调用一次 localInit?
【发布时间】:2018-08-08 13:32:36
【问题描述】:

我一直在试验 Parallel.For。特别是支持线程本地数据的重载,例如

public static System.Threading.Tasks.ParallelLoopResult For (long fromInclusive, long toExclusive, System.Threading.Tasks.ParallelOptions parallelOptions, Func localInit, Func body, Action localFinally);

根据documentation

localInit 委托为参与循环执行的每个线程调用一次

但是我认为我下面的例子与之矛盾

var threads = new ConcurrentBag<int>();
ValueTuple LocalInit()
{
    threads.Add(Thread.CurrentThread.ManagedThreadId);
    return new System.ValueTuple();
}
ValueTuple Body(long i, ParallelLoopState _, ValueTuple state) 
{
    Thread.Sleep(100);
    return state;
}
void LocalFinally(ValueTuple state) { };

Parallel.For(0L, 1000L, new ParallelOptions(), LocalInit, Body, LocalFinally);

Console.WriteLine($"{threads.Count} inits between {threads.Distinct().Count()} threads");

它打印一条消息,例如

13 个线程之间的 79 个初始化

发生了什么事?

【问题讨论】:

  • 你正在阻塞线程,迫使Parallel.For创建更多线程来处理输入。 Parallel. 方法用于数据并行。他们会从大致与核心数量一样多的任务开始,如果检测到任务被阻止,则会添加更多任务。
  • 一个更好的例子是使用一个大循环和Body,它实际上做了一些事情,或者一个不让线程进入睡眠状态的 Spinwait

标签: .net task-parallel-library parallel.foreach


【解决方案1】:

尝试记录任务 id Task.CurrentId 而不是线程 id。

var threads = new ConcurrentBag<int>();
var tasks = new ConcurrentBag<int>();
ValueTuple LocalInit()
{
    threads.Add(Thread.CurrentThread.ManagedThreadId);
    tasks.Add(Task.CurrentId ?? throw new Exception());
    return new System.ValueTuple();
}
ValueTuple Body(long i, ParallelLoopState _, ValueTuple state) 
{
    Thread.Sleep(100);
    return state;
}
void LocalFinally(ValueTuple state) { };

Parallel.For(0L, 1000L, new ParallelOptions(), LocalInit, Body, LocalFinally);

Console.WriteLine($"{threads.Count} inits between {threads.Distinct().Count()} threads");
Console.WriteLine($"{tasks.Count} inits between {tasks.Distinct().Count()} tasks");

打印出来

16 个线程之间的 87 个初始化
87 个任务之间的 87 个初始化

文档有误。他们应该说

localInit 委托为参与循环执行的每个任务调用一次

任务可以多于线程。总是线程数任务数迭代次数

【讨论】:

  • 问题在于代码本身,而不是文档。它停止线程,强制Parallel. 使用另一个线程。如果任务花费太长时间,Parallel. 可以使用比核心更多的任务
  • 是的 - Thread.Sleep(100) 在 i7 上显示 77 个初始化和 14 个线程。 Thread.SpinWait(100) 显示 7/7
  • 代码也可能由于其他原因而阻塞,这可能不受开发人员的控制。这里的重点是有 87 个初始化与 16 个线程。这不是通过代码阻塞来解释的。发生的情况是 16 个不同的线程被重用于执行 87 个任务。 LocalInit() 为每个任务而不是每个线程调用一次,如答案中所述。设计是合理的(当一个线程被重用时,它必须重新初始化)但它应该被正确记录。
猜你喜欢
  • 2014-09-21
  • 2011-11-18
  • 1970-01-01
  • 2019-12-22
  • 2014-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-26
相关资源
最近更新 更多