【问题标题】:How to store results from tasks running in threadpool?如何存储线程池中运行的任务的结果?
【发布时间】:2017-04-06 18:06:14
【问题描述】:

我对线程池效率有疑问。我不确定我是否理解整个概念。在问这个问题之前我做了很多阅读,我知道如果你有很多小的、相对快速的函数并且更重要的是非阻塞任务,线程池是一个很好的解决方案。在线程池中使用lock 非常糟糕。

这是我的问题:如何从线程池函数返回值?如果你有 functions 来运行它们可能会产生一些结果,对吧?最好将这些结果存储在某个地方。 在哪里?

我正在运行 c.a.线程池中有 200k 个非常快速的函数。我将结果存储在List 中。我当然必须这样做:

lock(lockobj)
{
    myList.Add(result);
}

那么,这是正确的方法吗?我的意思是,如果你的 functions 返回一些东西,你必须将它们存储在某种集合中。它必须是一个阻塞集合。所以,我开始想……“线程池中的阻塞非常小,但你必须这样做,至少一次 - 在每个函数的末尾

如何存储/返回线程池中运行的函数的结果?

谢谢! JB

编辑:“功能”我的意思是......

 ThreadPool.QueueUserWorkItem(state =>
 {
    Result r = function(); // previously named "Task"
    lock(lockobj)
    {
        allResults.Add(r);
    }
 }

【问题讨论】:

  • 是的,它是正确的方式。计时,这应该是您进行任何性能调整的基准。如果您发现无锁方式也可以测量它们。始终衡量您是否有性能调优的想法。始终有一个基线(最简单的工作代码)
  • 你试过ConcurrentBag吗?
  • 如果您对锁写不正确有任何疑问,可以使用ConcurrentQueue
  • 你几乎不需要直接访问线程池;您应该使用构建在它之上的更高级别的工具。您使用哪种工具将取决于您实际的高阶操作是什么。
  • @Bem,您正在交替使用TaskThreadPool,您知道它们是不同的东西吗? Task 可能最终成为一个线程,但很可能不会。这是由 .NET 框架管理的。至于线程安全的集合,前面的评论已经回答过了。

标签: c# .net multithreading task-parallel-library threadpool


【解决方案1】:

如果您不想阻塞ThreadPool 线程,请使用无锁方法。当您将项目排入队列时,ConcurrentQueue 当前是无锁的(从 .NET 4.6.2 开始)。

所以只需这样做:

public static ConcurrentQueue<Result> AllResults { get; } = new ConcurrentQueue<Result>();

ThreadPool.QueueUserWorkItem(state =>
{
    Result r = function();
    AllResults.Enqueue(r);
}

这将保证您不会阻塞 ThreadPool 线程。

【讨论】:

  • 对于我们这些有兴趣了解其工作原理的人 - referencesource.microsoft.com/#mscorlib/system/Collections/…。原子增量和自旋锁。顺便说一句,c# lock 是如何工作的 - 请参阅stackoverflow.com/questions/5111779/…
  • @pm100 - 这是完全不正确的。 lock 关键字是 Monitor.EnterMonitor.Exit 的同义词,两者都可能导致上下文切换和阻塞。是的,他们确实尝试旋转,并且确实使用互锁。但是它们会导致上下文切换并使线程处于阻塞(等待)状态。这个解决方案没有。避免(相对)非常昂贵的上下文切换是使用无锁算法的一个重要原因。更不用说从用户模式到内核模式的转换以及诸如死锁之类的各种其他事情。
  • 你读过这篇文章吗,说如果没有争用,CLR 会在用户模式代码中实现锁——只有在争用时才会进行内核切换。这将是 >90% 的情况
  • @pm100 我非常清楚这一切是如何运作的。无需阅读(您的链接确实提到了阻塞,仅供参考)。 OP 大胆地指出他不愿意阻止(尤其是ThreadPool),我不怪他。所以我提出了一个简单的非阻塞解决方案。问题解决了。
  • @Zer0 谢谢,看起来很有希望 - 我会检查一下,让你们知道它是怎么做的
【解决方案2】:

任何类型的线程安全/同步的集合都可以。 .net 框架中有很多。

您也可以使用 volatile 变量在多个线程之间存储数据 - 但这通常被认为是一种不好的做法强>.

另一种方法是将这些操作安排在可以产生结果的任务上,它们默认在线程池上运行,您可以通过等待方法并检查返回的任务的结果来获取返回值。

最后,您可以编写自己的代码,以便使用 锁、信号量、互斥锁等

来同步对某些代码区域/变量等的访问

【讨论】:

  • 小心使用volatile。它当然不能保证线程安全。
  • 是的,我同意,使用 volatile 通常是一种不好的做法,这就是为什么我建议使用 lock/semaphores/mutex 作为替代方案。我将编辑我的回复并删除不稳定的建议。
  • @ClaudiuCojocaru 感谢您的帮助,但我写道,我想避免线程池中非常糟糕的锁定、阻塞、信号量等。
猜你喜欢
  • 2011-05-30
  • 2014-06-14
  • 2017-01-06
  • 2016-06-14
  • 2013-07-22
  • 2015-07-06
  • 1970-01-01
  • 2022-11-25
  • 1970-01-01
相关资源
最近更新 更多