我一直在尝试学习C#中的多线程编程,我很困惑何时最好使用线程池而不是创建自己的线程。一本书建议仅为小任务使用线程池(无论这意味着什么),但我似乎无法找到任何真正的指导方针。在做出这个编程决策时你会考虑哪些因素?
答案 0 :(得分:48)
我建议您在C#中使用线程池的原因与任何其他语言相同。
如果要限制运行的线程数或不希望创建和销毁它们的开销,请使用线程池。
通过小任务,您阅读的书意味着生命周期短的任务。如果创建一个只运行一秒钟的线程需要十秒钟,那就是你应该使用池的地方(忽略我的实际数字,这是重要的比率)。
否则,您将花费大量时间来创建和销毁线程,而不是简单地完成他们打算做的工作。
答案 1 :(得分:45)
如果您有许多需要持续处理的逻辑任务,并且您希望并行完成,请使用pool + scheduler。
如果您需要同时执行与IO相关的任务,例如从远程服务器或磁盘访问下载内容,但需要每隔几分钟执行一次,那么请创建自己的线程并在完成后终止它们。< / p>
编辑:关于一些注意事项,我使用线程池进行数据库访问,物理/模拟,AI(游戏),以及在处理大量用户定义任务的虚拟机上运行的脚本任务。
通常一个池由每个处理器2个线程组成(现在很可能是4个),但是如果你知道需要多少线程,你可以设置你想要的线程数量。
编辑:创建自己的线程的原因是由于上下文更改(当线程需要交换进出进程时,以及它们的内存)。无用的上下文更改,比如当你没有使用你的线程时,只要让他们坐在那里就像人们所说的那样,可以很容易地使你的程序性能降低一半(比如你有3个睡眠线程和2个活动线程)。因此,如果那些下载线程只是在等待,那么他们就会占用大量CPU并为您的实际应用程序冷却缓存
答案 2 :(得分:28)
以下是.Net中线程池的一个很好的总结:http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx
当你不应该使用线程池并开始自己的线程时,帖子也有一些要点。
答案 3 :(得分:14)
我强烈推荐阅读这本免费电子书: Threading in C# by Joseph Albahari
至少阅读“使用入门”部分。电子书提供了很好的介绍,并包含了大量高级线程信息。
知道是否使用线程池只是一个开始。接下来,您需要确定哪种方法最适合您的线程池:
这本电子书解释了这些,并建议何时使用它们而不是创建自己的主题。
答案 4 :(得分:8)
线程池旨在减少线程之间的上下文切换。考虑一个运行多个组件的进程。这些组件中的每一个都可以创建工作线程。进程中的线程越多,上下文切换浪费的时间就越多。
现在,如果每个组件都将项目排队到线程池,那么上下文切换开销就会少得多。
线程池旨在最大限度地提高CPU(或CPU内核)的工作量。这就是为什么默认情况下,线程池会为每个处理器旋转多个线程。
在某些情况下,您不希望使用线程池。如果您正在等待I / O,或等待事件等,那么您将占用该线程池线程,并且其他任何人都无法使用它。同样的想法适用于长时间运行的任务,尽管构成长期运行的任务是主观的。
Pax Diablo也是一个好主意。旋转线程不是免费的。它需要时间,并且它们为堆栈空间消耗额外的内存。线程池将重新使用线程来分摊此成本。
注意:您询问了如何使用线程池线程下载数据或执行磁盘I / O.您不应该使用线程池线程(出于上面概述的原因)。而是使用异步I / O(又名BeginXX和EndXX方法)。对于FileStream
和BeginRead
的{{1}}。对于EndRead
和HttpWebRequest
的{{1}}。它们使用起来比较复杂,但它们是执行多线程I / O的正确方法。
答案 5 :(得分:6)
请注意.NET线程池可能阻止其处理的任何重要,变量或未知部分的操作,因为它容易出现线程饥饿。考虑使用.NET并行扩展,它在线程操作上提供了大量的逻辑抽象。它们还包括一个新的调度程序,它应该是对ThreadPool的改进。见here
答案 6 :(得分:3)
仅将线程池用于小任务的一个原因是线程池线程数量有限。如果长时间使用一个,那么它会阻止该线程被其他代码使用。如果多次发生这种情况,则线程池可能会耗尽。
使用线程池可能会产生微妙的影响 - 例如,某些.NET计时器使用线程池线程并且不会触发。
答案 7 :(得分:2)
如果您的后台任务将持续很长时间,例如应用程序的整个生命周期,那么创建自己的线程是合理的。如果你需要在一个线程中完成短作业,那么使用线程池。
在创建多个线程的应用程序中,创建线程的开销变得很大。使用线程池创建一次线程并重用它们,从而避免线程创建开销。
在我所使用的应用程序中,从创建线程转换为使用短线程线程的线程池确实有助于实现应用程序。
答案 8 :(得分:2)
为了同时执行单元的最高性能,编写自己的线程池,在启动时创建一个Thread对象池并转到阻塞(以前挂起),等待上下文运行(具有标准的对象)您的代码实现的接口。)
关于Tasks vs. Threads与.NET ThreadPool的文章很多都无法真正为您提供做出决策所需的内容。但是当你比较它们时,Threads会胜出,特别是一个Threads池。它们在CPU之间分布最佳,并且启动速度更快。
应该讨论的事实是Windows的主要执行单元(包括Windows 10)是一个线程,并且OS上下文切换开销通常可以忽略不计。简而言之,我无法找到许多这些文章的令人信服的证据,无论文章是通过保存上下文切换还是更好的CPU使用率来获得更高的性能。
现在有点现实主义:
我们大多数人都不需要我们的应用程序是确定性的,而且我们大多数人都没有线程的艰难背景,例如通常伴随着开发操作系统。我上面写的不是初学者。
所以最重要的是讨论易于编程的内容。
如果你创建自己的线程池,你需要做一些写作,因为你需要关注跟踪执行状态,如何模拟暂停和恢复,以及如何取消执行 - 包括在应用程序范围内的关闭。您可能还需要关心是否要动态扩展池以及池将具有哪些容量限制。我可以在一小时内编写这样一个框架,但那是因为我已经这么做了很多次。
编写执行单元的最简单方法可能是使用Task。任务的美妙之处在于,您可以在代码中创建一个并在线启动(尽管可能需要谨慎)。您可以在取消任务时传递取消令牌来处理。此外,它使用promise方法来链接事件,您可以让它返回特定类型的值。此外,通过异步和等待,存在更多选项,您的代码将更加便携。
从本质上讲,了解Tasks vs. Threads与.NET ThreadPool的优缺点非常重要。如果我需要高性能,我将使用线程,我更喜欢使用自己的池。
一种简单的比较方法是启动512个线程,512个任务和512个ThreadPool线程。你会在Threads的开头找到一个延迟(因此,为什么要写一个线程池),但是所有512个线程都将在几秒钟内运行,而Tasks和.NET ThreadPool线程需要几分钟时间才能启动。 / p>
以下是此类测试的结果(i5四核,16 GB RAM),每30秒运行一次。执行的代码在SSD驱动器上执行简单的文件I / O.
答案 9 :(得分:1)
如果要处理的任务多于可用线程,则线程池非常有用。
您可以将所有任务添加到线程池,并指定在特定时间可以运行的最大线程数。
在MSDN上查看this页面: http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx
答案 10 :(得分:1)
如果可以,请始终使用线程池,尽可能在最高抽象级别上工作。线程池隐藏了为你创建和销毁线程,这通常是一件好事!
答案 11 :(得分:1)
大多数情况下,您可以使用池,因为您避免了创建线程的昂贵过程。
但是在某些情况下,您可能想要创建一个线程。例如,如果您不是唯一使用线程池的人,并且您创建的线程是长期存在的(以避免消耗共享资源),或者例如,如果您想控制线程的堆栈大小。
答案 12 :(得分:1)
不要忘记调查后台工作人员。
我发现很多情况,它给了我正确的东西而没有繁重的工作。
干杯。
答案 13 :(得分:0)
每当我需要在另一个线程上执行某些操作时,我通常会使用Threadpool,并且在运行或结束时并不关心。像登录或甚至后台下载文件的东西(虽然有更好的方法来做异步风格)。当我需要更多控制时,我使用自己的线程。我还发现当我有多个命令需要在&gt; 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();
}
}
}
答案 14 :(得分:0)
我希望一个线程池能够以尽可能小的延迟在核心之间分配工作,并且不必与其他应用程序一起使用。我发现.NET线程池的性能并不尽如人意。我知道我想要每个核心一个线程,所以我编写了自己的线程池替代类。该代码是作为另一个StackOverflow问题over here的答案而提供的。
对于最初的问题,线程池对于将重复计算分解为可并行执行的部分非常有用(假设它们可以并行执行而不会改变结果)。手动线程管理对于UI和IO等任务非常有用。