什么时候在C#中使用线程池?
我一直在尝试在Cand中学习多线程编程,我困惑于何时最好使用线程池而不是创建自己的线程。一本书建议仅将线程池用于小型任务(无论这意味着什么),但我似乎找不到任何真正的指导方针。在做出此编程决定时,需要考虑哪些注意事项?
解决方案
当我们要处理的任务多于可用线程时,线程池非常有用。
我们可以将所有任务添加到线程池中,并指定在特定时间可以运行的最大线程数。
在MSDN上查看此页面:
http://msdn.microsoft.com/zh-CN/library/3dasc8as(VS.80).aspx
如果可能,请始终使用线程池,并尽可能以最高的抽象水平进行工作。线程池为我们隐藏了创建和销毁线程的过程,这通常是一件好事!
如果我们有很多需要不断处理的逻辑任务,并且希望并行执行,请使用pool + scheduler。
如果我们需要同时执行与IO相关的任务(例如从远程服务器下载内容或者访问磁盘),但需要每隔几分钟执行一次,那么请创建自己的线程并在完成后将其杀死。
编辑:关于一些注意事项,我将线程池用于数据库访问,物理/模拟,AI(游戏),以及用于在处理许多用户定义任务的虚拟机上运行的脚本任务。
通常,一个处理器池由每个处理器2个线程组成(现在大概是4个),但是,如果知道需要多少线程,则可以设置所需的线程数量。
编辑:制作自己的线程的原因是由于上下文的更改,(即当线程需要与它们的内存一起换入和换出该进程时)。进行无用的上下文更改(例如,当我们不使用线程时),就像他们可能说的那样,将它们闲置一圈,很容易会使程序的性能降低一半(例如我们有3个睡眠线程和2个活动线程)。因此,如果这些下载线程只是在等待,它们正在消耗大量的CPU并冷却实际应用程序的缓存
我建议我们出于与其他任何语言相同的原因在C中使用线程池。
当我们想限制正在运行的线程数或者不想创建和销毁它们的开销时,请使用线程池。
对于小任务,我们读的书意味着寿命短的任务。如果创建一个仅运行一秒钟的线程需要十秒钟,那么这就是我们应该使用池的地方(忽略我的实际数字,这是重要的比率)。
否则,我们将花费大量时间来创建和销毁线程,而不是简单地完成它们打算执行的工作。
这是.Net中线程池的一个不错的摘要:http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx
这篇文章还指出了什么时候不应该使用线程池而应该启动自己的线程。
线程池旨在减少线程之间的上下文切换。考虑一个运行着多个组件的过程。这些组件中的每一个都可以创建工作线程。进程中的线程越多,上下文切换所浪费的时间就越多。
现在,如果每个组件都将项目排队到线程池中,则上下文切换开销将大大减少。
线程池旨在最大程度地跨CPU(或者CPU内核)完成工作。这就是为什么默认情况下,线程池在每个处理器中增加多个线程的原因。
在某些情况下,我们不想使用线程池。如果我们正在等待I / O或者正在等待事件等,那么我们将占用该线程池线程,其他任何人都无法使用它。同样的想法也适用于长时间运行的任务,尽管构成长时间运行任务的是主观的。
Pax Diablo也很不错。加速线程不是免费的。这需要时间,并且它们会占用额外的内存来占用其堆栈空间。线程池将重新使用线程以分摊此费用。
注意:我们询问有关使用线程池线程下载数据或者执行磁盘I / O的问题。为此,我们不应使用线程池线程(由于上述原因)。而是使用异步I / O(也称为BeginXX和EndXX方法)。对于FileStream
来说应该是BeginRead
和EndRead
。对于一个HttpWebRequest,它将是BeginGetResponse和EndGetResponse。它们使用起来更加复杂,但是它们是执行多线程I / O的正确方法。
在大多数情况下,我们可以使用池,因为可以避免创建线程的昂贵过程。
但是,在某些情况下,我们可能需要创建一个线程。例如,如果我们不是唯一使用线程池的人,并且创建的线程是长期存在的(以避免消耗共享资源),或者例如,如果我们想控制线程的堆栈大小。
仅将线程池用于小型任务的一个原因是线程池线程数有限。如果长时间使用一个线程,则它将阻止该线程被其他代码使用。如果多次发生这种情况,则线程池可能会用完。
例如,耗尽线程池可能会产生微妙的影响,某些.NET计时器使用线程池线程并且不会触发。
如果后台任务可以生存很长时间,例如在应用程序的整个生命周期中,那么创建自己的线程是合理的。如果我们有需要在线程中完成的简短工作,请使用线程池。
在创建多个线程的应用程序中,创建线程的开销非常大。使用线程池仅创建一次线程并重新使用它们,从而避免了线程创建的开销。
在我研究的应用程序中,从创建线程到将线程池用于短寿命线程的转变确实有助于应用程序的吞吐量。
注意.NET线程池中的操作可能会阻塞其处理中的任何重要,可变或者未知部分,因为它很容易出现线程不足的情况。考虑使用.NET并行扩展,该扩展在线程操作上提供了大量的逻辑抽象。它们还包括一个新的调度程序,应该是对ThreadPool的改进。看这里
我通常在需要在另一个线程上执行某些操作时就使用Threadpool,而实际上并不关心它何时运行或者结束。诸如日志记录或者什至是后台下载文件之类的东西(尽管有更好的方式来执行异步样式)。当我需要更多控制权时,可以使用自己的线程。当我有多个需要在大于1个线程中处理的命令时,我发现使用Threadsafe队列(自行攻击)来存储"命令对象"也很不错。因此,我们可能会拆分Xml文件并将每个元素放入队列中,然后让多个线程对这些元素进行一些处理。我在uni(VB.net!)中写了这样的队列方式,已经转换为C#。我出于特殊原因将其包括在下面(此代码可能包含一些错误)。
using System.Collections.Generic; using System.Threading; namespace ThreadSafeQueue { public class ThreadSafeQueue<T> { private Queue<T> _queue; public ThreadSafeQueue() { _queue = new Queue<T>(); } public void EnqueueSafe(T item) { lock ( this ) { _queue.Enqueue(item); if ( _queue.Count >= 1 ) Monitor.Pulse(this); } } public T DequeueSafe() { lock ( this ) { while ( _queue.Count <= 0 ) Monitor.Wait(this); return this.DeEnqueueUnblock(); } } private T DeEnqueueUnblock() { return _queue.Dequeue(); } } }
不要忘记调查背景工作者。
我发现在很多情况下,它都能满足我的需求而无需繁重的工作。
干杯。
我希望线程池能以尽可能少的延迟在内核之间分配工作,而不必与其他应用程序很好地配合。我发现.NET线程池的性能不尽如人意。我知道每个内核需要一个线程,所以我编写了自己的线程池替代类。该代码是这里另一个StackOverflow问题的答案。
对于最初的问题,线程池对于将重复的计算分解为可以并行执行的部分很有用(假设它们可以并行执行而不改变结果)。手动线程管理对于诸如UI和IO之类的任务很有用。
我强烈建议我们阅读这本免费的电子书:
Cby约瑟夫·阿尔巴哈里(Joseph Albahari)的主题
至少阅读"入门"部分。该电子书提供了很好的介绍,并且还包含了大量的高级线程信息。
知道是否使用线程池只是一个开始。接下来,我们将需要确定最适合我们需求的进入线程池的方法:
- 任务并行库(.NET Framework 4.0)
- ThreadPool.QueueUserWorkItem
- 异步代表
- 后台工作者
这本电子书解释了所有这些内容,并建议何时使用它们以及创建自己的线程。