【问题标题】:Task Parallel Library Recycling in-use Threads任务并行库回收正在使用的线程
【发布时间】:2011-04-29 01:11:10
【问题描述】:

我们的系统会在写入的每条消息中写入 ManagedThreadID,多年来我们一直使用它来帮助区分日志中的多个特定工作单元。到目前为止,一切顺利。

现在,我们开始使用任务并行库并注意到一个有趣的效果:

    public static void Main(string[] args) {
        WriteLine("BEGIN");

        Parallel.For(0, 32, (index) => {
            WriteLine("  Loop " + index.ToString());

        });

        WriteLine("END");
    }

输出类似于:

ThreadID=1, Message=BEGIN
ThreadID=1, Message=  Loop 0
ThreadID=3, Message=  Loop 16
ThreadID=3, Message=  Loop 17
...
ThreadID=4, Message=  Loop 4
ThreadID=4, Message=  Loop 5
ThreadID=1, Message=  Loop 8
ThreadID=1, Message=  Loop 9
ThreadID=1, Message=  Loop 10
ThreadID=3, Message=  Loop 21
ThreadID=4, Message=  Loop 6
...
ThreadID=3, Message=  Loop 24
ThreadID=3, Message=  Loop 25
ThreadID=1, Message=  Loop 11
ThreadID=1, Message=  Loop 12
ThreadID=1, Message=  Loop 13
ThreadID=1, Message=  Loop 31
ThreadID=3, Message=  Loop 26
...
ThreadID=3, Message=  Loop 30
ThreadID=1, Message=END

您会注意到主线程的 ThreadID(标记为“BEGIN”)有时会在 Loop 线程中回收。

我的问题是:这是否会发生在其他任何地方——例如线程池或使用任务并行库的其他功能时?我花了很多时间试图找出引发这种行为的其他方法,但我做不到。

这里的问题是,如果我们不能再依赖 ThreadID(我们有很多工具可以依赖这种行为),那么我们将避免使用 Parallel.For。但如果问题会以其他方式表现出来,我们需要弄清楚如何避免它们直到我们改进我们的日志记录策略和工具支持。

如果有其他方法可以引发该行为,我想了解一下,以便确定我们的任何用法是否符合此类条件,以便我们进行相应的纠正。更重要的是,我可以获得一个示例程序来激发行为并研究我们工具中的任何副作用。

【问题讨论】:

    标签: multithreading .net-4.0 task-parallel-library


    【解决方案1】:

    Parallel.For 确实在调用线程上运行了一项工作任务。理由是,由于调用线程必须等到并行循环完成,它还不如参与并行操作。

    就任务并行库的其他功能而言,阻塞的方法通常会使用调用线程。因此,Parallel.For、Parallel.ForEach、Parallel.Invoke 和阻塞 PLINQ 查询都将重用调用线程作为工作线程之一。另一方面,只是“启动”一些工作并立即返回的操作(如 Task.Factory.StartNew、Threadpool.QueueUserWorkItem 和非阻塞 PLINQ 查询)不能使用调用线程。

    作为一种解决方法,您可以在任务中运行 Parallel.For 并等待任务:

    public static void Main(string[] args) {
        WriteLine("BEGIN");
        Task.Factory.StartNew(() =>
            Parallel.For(0, 32, (index) => {
                WriteLine("  Loop " + index.ToString());
            })
        ).Wait();
        WriteLine("END");
    }    
    

    警告:如果从 ThreadPool 线程调用 Task.Factory.StartNew(),上述解决方法将不起作用。在这种情况下,Wait 调用可能最终会在调用 ThreadPool 线程上内联执行任务。

    【讨论】:

    • 感谢您的回答。很高兴知道它是孤立的(目前)。
    【解决方案2】:

    你必须不使用 TPL 来避免这种行为

    听起来你需要找到一种不同的方法来识别除了 ThreadID 之外的工作单元,如果你不能重写代码来传递某种标识符,可能使用logical call context 来传递有关当前线程的信息。

    【讨论】:

    • 我已经实现了一个类似链接中描述的功能。我希望在一个月内推出它。但是很高兴知道问题与阻塞并行抽象无关。我们暂时可以避开它们。
    猜你喜欢
    • 2012-06-25
    • 1970-01-01
    • 2012-08-24
    • 2014-05-31
    • 1970-01-01
    • 2015-01-14
    • 1970-01-01
    • 2015-06-26
    • 2019-03-13
    相关资源
    最近更新 更多