什么时候在C#中使用线程池?
我一直在尝试学习C#中的multithreading编程,我很困惑什么时候最好使用线程池,创build自己的线程。 一本书build议使用一个线程池只用于小任务(不pipe这意味着什么),但我似乎无法find任何真正的指导方针。 在做出这个编程决定时,你需要考虑什么?
如果您有许多需要不断处理的逻辑任务,并且希望以并行方式使用池+调度程序。
如果你需要同时完成IO相关的任务,例如从远程服务器或磁盘访问下载东西,但每隔几分钟就需要做一次这样的事情,那么创build自己的线程并在完成后就杀掉它们。
编辑:关于一些考虑,我使用线程池进行数据库访问,物理/仿真,AI(游戏)和脚本任务运行在虚拟机上处理大量用户定义的任务。
通常情况下,一个池由每个处理器2个线程组成(如今可能是4个),但是如果知道需要多less个线程,则可以设置所需线程的数量。
编辑:使自己的线程的原因是因为上下文的变化,(当线程需要交换进程,以及他们的内存)。 无用的上下文变化,比如说当你不使用你的线程时,只要把它们放在一边,就可以轻松地实现一半的程序性能(比如你有3个睡眠线程和2个活动线程)。 因此,如果这些下载线程正在等待,他们正在吃掉大量的CPU,并为真正的应用程序冷却caching
我build议你在C#中使用线程池的原因与任何其他语言。
当你想限制正在运行的线程数量或者不想创build和销毁它们的开销时,使用一个线程池。
通过小任务,你阅读的书意味着短寿的任务。 如果创build一个只运行一秒的线程需要十秒钟的时间,那么您应该使用一个池(忽略我的实际数字,这是计数的比例)。
否则,你花大量的时间来创build和销毁线程,而不是简单地做他们打算做的工作。
下面是.Net中线程池的一个很好的总结: http : //blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx
这篇文章还有一些要点,你不应该使用线程池,而是开始自己的线程。
我强烈推荐阅读这本免费的电子书: Joseph Albahari在C#中的Threading
至less阅读“入门”部分。 电子书提供了一个很好的介绍,并包含丰富的高级线程信息。
知道是否使用线程池只是一个开始。 接下来,您需要确定进入线程池的哪种方法最适合您的需求:
- 任务并行库(.NET Framework 4.0)
- ThreadPool.QueueUserWorkItem
- asynchronous代表
- 的BackgroundWorker
这本电子书解释了这些,并build议何时使用它们与创build自己的线程。
线程池旨在减less线程之间的上下文切换。 考虑一个有几个组件运行的进程。 每个组件都可以创build工作线程。 进程中的线程越多,上下文切换浪费的时间就越多。
现在,如果这些组件中的每一个都将项目排入线程池,那么上下文切换开销将会减less很多。
线程池旨在最大限度地发挥您的CPU(或CPU核心)所做的工作。 这就是为什么,默认情况下,线程池为每个处理器启动多个线程。
有些情况下你不想使用线程池。 如果你正在等待I / O,或等待一个事件等,那么你绑定该线程池线程,它不能被其他人使用。 同样的想法适用于长时间运行的任务,但构成长期运行任务的是主观的。
Pax暗黑破坏神也是一个很好的观点。 纺纱线程不是免费的。 这需要时间,并且为堆栈空间消耗额外的内存。 线程池将重新使用线程分摊这个成本。
注意:您询问使用线程池线程来下载数据或执行磁盘I / O。 你不应该使用这个线程池线程(由于我上面概述的原因)。 而是使用asynchronousI / O(即BeginXX和EndXX方法)。 对于将作为BeginRead
和EndRead
的FileStream
。 对于将是BeginGetResponse
和EndGetResponse
的HttpWebRequest
。 它们使用起来比较复杂,但它们是执行multithreadingI / O的正确方法。
当心.NET线程池的操作可能会阻塞任何重要的,可变的或未知的处理部分,因为它容易导致线程匮乏。 考虑使用.NET并行扩展,它们提供了相对于线程操作的很多逻辑抽象。 他们还包括一个新的调度程序,这应该是对ThreadPool的改进。 看到这里
使用线程池仅用于小任务的一个原因是线程池线程数量有限。 如果使用了很长时间,则会阻止该线程被其他代码使用。 如果这种情况发生很多次,那么线程池可能会用完。
使用线程池可能会产生微妙的影响 – 例如,一些.NET定时器使用线程池线程并且不会触发。
线程池是伟大的,当你有更多的任务来处理比可用的线程。
您可以将所有任务添加到线程池,并指定可以在特定时间运行的最大线程数。
看看MSDN上的这个页面: http : //msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx
如果可以,请始终使用线程池,尽可能在最高级别的抽象层次上工作。 线程池隐藏了创build和销毁线程,这通常是一件好事!
大多数情况下,您可以使用该池,因为避免了创build线程的昂贵过程。
但是,在某些情况下,您可能需要创build一个线程。 例如,如果您不是唯一使用线程池的线程,并且创build的线程是长期存在的(避免占用共享资源),或者例如想要控制线程的堆栈大小。
如果你有一个长期的后台任务,比如你的应用程序的整个生命周期,那么创build你自己的线程是一件合理的事情。 如果你有一个短线程需要在一个线程中完成,那么就使用线程池。
在创build多个线程的应用程序中,创build线程的开销变得很大。 使用线程池创build一次线程并重用它们,从而避免线程创build的开销。
在我工作的应用程序中,从创build线程变为使用短线程线程池确实帮助了应用程序的通过。
不要忘记调查后台工作人员。
我发现很多情况下,没有繁重的工作,我只想要自己想要的东西。
干杯。
为了在并发执行单元中实现最高性能,编写自己的线程池,在启动时创build一个Thread对象池,然后转到阻塞(以前挂起),等待上下文运行(具有由你的代码)。
所以很多关于任务和线程的文章与.NET的ThreadPool没有真正给你你需要做出决定的性能。 但是当你比较它们时,线程会赢,特别是线程池。 他们分布在CPU最好的,他们开始更快。
应该讨论的是,Windows(包括Windows 10)的主要执行单元是一个线程,OS上下文切换开销通常可以忽略不计。 简而言之,我无法find许多这些文章的令人信服的证据,这篇文章是否声称通过节省上下文切换或更好的CPU使用率来提高性能。
现在有点现实主义:
我们大多数人将不需要我们的应用程序是确定性的,我们大多数人不会有线程的困难背景,例如经常与开发一个操作系统。 我上面写的不是初学者。
所以最重要的是讨论什么是易于编程的。
如果你创build了自己的线程池,那么你需要做一些写操作,因为你需要关心跟踪执行状态,如何模拟暂停和恢复,以及如何取消执行 – 包括在应用程序范围内关掉。 您可能还必须关心您是否想要dynamic扩展您的池,以及池的容量限制。 我可以在一个小时内编写这样一个框架,但那是因为我做了很多次。
编写一个执行单元最简单的方法是使用一个Task。 Task的美妙之处在于你可以创build一个,并在你的代码中进行内联(尽pipe可能需要谨慎)。 您可以传递取消标记来处理何时取消任务。 另外,它使用promise方法来链接事件,并且可以让它返回一个特定types的值。 而且,在asynchronous和等待的情况下,存在更多的select,并且您的代码将更加便携。
从本质上讲,了解任务与线程与.NET ThreadPool的优缺点非常重要。 如果我需要高性能,我将使用线程,我更喜欢使用自己的池。
一个简单的比较方法是启动512个线程,512个任务和512个线程池线程。 你会发现在线程开始的延迟(因此,为什么写一个线程池),但所有512线程将在几秒钟内运行,而任务和.NET线程池线程需要几分钟的时间才能开始。
下面是这种testing的结果(i5四核16 GB的RAM),每30秒运行一次。 执行的代码在SSD驱动器上执行简单的文件I / O。
检测结果
当我需要在另一个线程上执行某些操作时,我通常使用线程池,并且在运行或结束时并不在乎。 就像日志logging或甚至后台下载文件(虽然有更好的方法来做这种asynchronous风格)。 当我需要更多的控制时,我使用自己的线程。 另外我发现使用线程安全队列(hack自己的)来存储“命令对象”是很好的,当我有多个命令,我需要在> 1线程工作。 所以你可能会拆分一个Xml文件,并把每个元素放在一个队列中,然后有多个线程在这些元素上做一些处理。 我写了这样一个队列的方式返回到单位(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问题的答案提供。
至于原来的问题,线程池对于将重复计算分解成可以并行执行的部分是有用的(假设它们可以并行执行而不改变结果)。 手动线程pipe理对UI和IO等任务非常有用。