127

我一直在尝试学习 C# 中的多线程编程,但我对何时最好使用线程池与创建自己的线程感到困惑。一本书建议仅将线程池用于小任务(无论这意味着什么),但我似乎找不到任何真正的指导方针。

线程池与创建自己的线程相比有哪些优缺点?每个都有哪些示例用例?

4

15 回答 15

49

出于与任何其他语言相同的原因,我建议您在 C# 中使用线程池。

当您想限制正在运行的线程数或不希望创建和销毁它们的开销时,请使用线程池。

小任务,你读的书是指生命周期短的任务。如果创建一个只运行一秒钟的线程需要十秒钟,那是您应该使用池的地方(忽略我的实际数字,这是重要的比率)。

否则,您会花费大量时间来创建和销毁线程,而不是简单地做它们打算做的工作。

于 2008-09-28T06:13:46.227 回答
48

如果您有许多需要持续处理的逻辑任务,并且您希望并行完成,请使用 pool+scheduler。

如果您需要同时执行与 IO 相关的任务,例如从远程服务器下载内容或访问磁盘,但需要每隔几分钟执行一次,然后创建自己的线程并在完成后终止它们。

编辑:关于一些考虑,我将线程池用于数据库访问、物理/模拟、AI(游戏)以及在处理大量用户定义任务的虚拟机上运行的脚本任务。

通常,一个池由每个处理器 2 个线程组成(现在可能是 4 个),但是如果您知道需要多少线程,您可以设置所需的线程数量。

编辑:创建自己的线程的原因是因为上下文的变化,(那是当线程需要交换进出进程以及它们的内存时)。有无用的上下文更改,比如当你不使用你的线程时,只是让它们像人们所说的那样闲置,很容易使你的程序性能减半(比如你有 3 个休眠线程和 2 个活动线程)。因此,如果那些下载线程只是在等待,它们会消耗大量 CPU 并为您的实际应用程序冷却缓存

于 2008-09-28T06:09:17.423 回答
28

这是.Net中线程池的一个很好的总结:http: //blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

该帖子还说明了何时不应使用线程池而应启动自己的线程。

于 2008-09-28T06:13:51.530 回答
14

我强烈推荐阅读这本免费的电子书: Joseph Albahari 的 C# 线程

至少阅读“入门”部分。这本电子书提供了很好的介绍,还包括大量高级线程信息。

知道是否使用线程池只是开始。接下来,您将需要确定哪种进入线程池的方法最适合您的需求:

  • 任务并行库 (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • 异步委托
  • 后台工作者

这本电子书解释了所有这些,并建议何时使用它们与创建自己的线程。

于 2010-09-29T14:09:37.757 回答
8

线程池旨在减少线程之间的上下文切换。考虑一个运行多个组件的进程。这些组件中的每一个都可以创建工作线程。进程中的线程越多,在上下文切换上浪费的时间就越多。

现在,如果这些组件中的每一个都将项目排队到线程池中,那么上下文切换开销就会少得多。

线程池旨在最大化跨 CPU(或 CPU 内核)完成的工作。这就是为什么默认情况下,线程池会为每个处理器启动多个线程。

在某些情况下,您不想使用线程池。如果您正在等待 I/O,或等待事件等,那么您会占用该线程池线程,并且其他任何人都无法使用它。同样的想法也适用于长期运行的任务,尽管构成长期运行的任务是主观的。

Pax Diablo 也提出了一个很好的观点。旋转线程不是免费的。这需要时间,并且它们会为堆栈空间消耗额外的内存。线程池将重用线程来分摊这个成本。

注意:您询问了有关使用线程池线程下载数据或执行磁盘 I/O 的问题。您不应该为此使用线程池线程(出于我上面概述的原因)。而是使用异步 I/O(又名 BeginXX 和 EndXX 方法)。对于FileStream那将是BeginReadand EndRead。对于HttpWebRequest那将是BeginGetResponseand EndGetResponse。它们使用起来更复杂,但它们是执行多线程 I/O 的正确方法。

于 2008-09-28T07:27:18.127 回答
6

当心 .NET 线程池中的操作可能会阻塞其处理的任何重要、可变或未知部分,因为它容易出现线程饥饿。考虑使用 .NET 并行扩展,它为线程操作提供了大量的逻辑抽象。它们还包括一个新的调度程序,这应该是对 ThreadPool 的改进。看这里

于 2008-09-30T10:59:15.477 回答
3

仅将线程池用于小任务的一个原因是线程池线程的数量有限。如果一个被使用了很长时间,那么它会阻止该线程被其他代码使用。如果这种情况发生很多次,那么线程池可能会被用完。

用完线程池可能会产生微妙的影响——例如,一些 .NET 计时器使用线程池线程并且不会触发。

于 2008-09-28T13:38:32.633 回答
2

如果您的后台任务会存在很长时间,例如应用程序的整个生命周期,那么创建自己的线程是合理的事情。如果您有需要在线程中完成的短作业,请使用线程池。

在创建许多线程的应用程序中,创建线程的开销会变得很大。使用线程池创建线程一次并重用它们,从而避免线程创建开销。

在我处理的一个应用程序中,从创建线程更改为使用线程池来处理短期线程确实有助于提高应用程序的吞吐量。

于 2008-09-28T13:47:55.543 回答
2

为了获得并发执行单元的最高性能,请编写您自己的线程池,其中一个线程对象池在启动时创建并进入阻塞(以前暂停),等待上下文运行(具有由实现的标准接口的对象你的代码)。

很多关于任务、线程和 .NET 线程池的文章都未能真正为您提供做出性能决定所需的内容。但是当你比较它们时,线程会胜出,尤其是线程池。它们在 CPU 中分布得最好,而且启动速度更快。

需要讨论的是Windows(包括Windows 10)的主要执行单元是线程,OS上下文切换开销通常可以忽略不计。简而言之,我一直无法找到其中许多文章的令人信服的证据,无论是文章声称通过节省上下文切换或更好的 CPU 使用来获得更高的性能。

现在有点现实主义:

我们中的大多数人不需要我们的应用程序是确定性的,而且我们中的大多数人都没有使用线程的硬知识背景,例如开发操作系统时经常会出现这种情况。我上面写的不适合初学者。

所以可能最重要的是讨论什么是易于编程的。

如果您创建自己的线程池,您将需要编写一些内容,因为您需要关注跟踪执行状态、如何模拟挂起和恢复以及如何取消执行——包括在应用程序范围内关闭。您可能还必须关心是否要动态增长池以及池将具有哪些容量限制。我可以在一个小时内写出这样一个框架,但那是因为我已经做过很多次了。

也许编写执行单元的最简单方法是使用任务。任务的美妙之处在于您可以创建一个并在代码中内联启动它(尽管可能需要谨慎)。当你想取消任务时,你可以传递一个取消令牌来处理。此外,它使用 Promise 方法链接事件,您可以让它返回特定类型的值。此外,使用 async 和 await,存在更多选项,并且您的代码将更具可移植性。

从本质上讲,了解 Tasks vs. Threads vs. .NET ThreadPool 的优缺点很重要。如果我需要高性能,我将使用线程,并且我更喜欢使用自己的池。

一个简单的比较方法是启动 512 个线程、512 个任务和 512 个线程池线程。您会发现 Threads 开始时会出现延迟(因此,为什么要编写线程池),但所有 512 个线程将在几秒钟内运行,而 Tasks 和 .NET ThreadPool 线程则需要几分钟才能全部启动。

以下是此类测试的结果(i5 四核,16 GB RAM),每运行 30 秒。执行的代码在 SSD 驱动器上执行简单的文件 I/O。

测试结果

于 2017-04-27T15:42:54.570 回答
1

当您要处理的任务多于可用线程时,线程池非常有用。

您可以将所有任务添加到线程池中,并指定在某个时间可以运行的最大线程数。

在 MSDN 上查看页面:http: //msdn.microsoft.com/en-us/library/3dasc8as (VS.80).aspx

于 2008-09-28T06:07:46.350 回答
1

如果可以,请始终使用线程池,尽可能在最高抽象级别上工作。线程池为您隐藏创建和销毁线程,这通常是一件好事!

于 2008-09-28T06:08:25.040 回答
1

大多数情况下,您可以使用池来避免创建线程的昂贵过程。

但是在某些情况下,您可能想要创建一个线程。例如,如果您不是唯一使用线程池的人,并且您创建的线程是长期存在的(以避免消耗共享资源),或者例如,如果您想控制线程的堆栈大小。

于 2008-09-28T08:01:06.780 回答
1

不要忘记调查后台工作人员。

我发现在很多情况下,它给了我我想要的东西,而不需要繁重的工作。

干杯。

于 2009-06-26T05:48:24.447 回答
0

每当我需要在另一个线程上做某事并且并不真正关心它何时运行或结束时,我通常会使用线程池。诸如日志记录甚至后台下载文件之类的东西(尽管有更好的方法可以实现异步风格)。当我需要更多控制时,我会使用自己的线程。我还发现,当我有多个需要在 >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();
        }
    }
}
于 2008-10-16T13:42:47.570 回答
0

我想要一个线程池以尽可能少的延迟在内核之间分配工作,并且不必与其他应用程序很好地配合。我发现 .NET 线程池的性能并没有达到应有的水平。我知道我希望每个核心有一个线程,所以我编写了自己的线程池替代类。该代码是作为对此处另一个 StackOverflow 问题的回答而提供的。

至于最初的问题,线程池对于将重复计算分解成可以并行执行的部分很有用(假设它们可以在不改变结果的情况下并行执行)。手动线程管理对于 UI 和 IO 等任务很有用。

于 2010-02-08T03:44:21.660 回答