为什么不改变线程池(或任务)线程的优先级?

27

在网络和Stack Overflow上有许多地方不鼓励更改ThreadPool线程或TPL Task的优先级。特别地:

"您无法控制线程池线程的状态和优先级。
运行时管理线程池。您无法控制线程调度,也不能更改线程的优先级。"

"您不应更改PoolThread的Culture、Priority或其他属性。就像您不会给租来的汽车喷漆或重新装修一样。"

"有几种情况下适合创建和管理自己的线程而不是使用线程池线程:(例如...)您需要一个具有特定优先级的线程。"

"线程池中的每个线程都以默认优先级运行,并且更改ThreadPriority的代码没有任何作用。"

但是实际上很容易这样做,并且调试器显示更改似乎确实生效(在可以读回该值的范围内)。

Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
所以问题是,这个特定禁忌的具体原因是什么?我的猜测是:这样做会干扰池的精细负载平衡假设。但这并不能解释为什么有些来源说你不能改变它。
6个回答

22

线程池,特别是.NET 4.0的线程池,有许多技巧并且是一个相当复杂的系统。加入任务和任务调度器、工作窃取和各种其他东西,实际情况是你不知道发生了什么。线程池可能会注意到您的任务正在等待I/O,并决定在您的任务上快速安排一些事情或暂停您的线程以运行优先级更高的任务。 您的线程可能某种方式成为优先级更高的线程的依赖项(您可能不知道),并最终导致死锁。您的线程可能以某种异常的方式死亡,并无法恢复优先级。

如果您有一个长时间运行的任务,认为您的线程应该具有较低的优先级,则线程池可能不适合您。虽然算法已经在.NET 4.0中得到改进,但它仍然最适用于短期任务,其中创建新线程的成本与任务的长度不成比例。如果您的任务运行超过一秒钟或两秒钟,则创建新线程的成本微不足道(虽然管理可能会很烦人)。


9

我已经对缩小尺寸的线程池进行了一些实验,似乎表明线程在返回池中后其优先级会被重置回正常水平。关于线程池的这个资源也似乎证实了这一点。因此即使你这么做,影响似乎非常有限。


2
降低优先级也可能会导致意想不到的后果。比如,在应用程序中安排了一个任务,同时调用了库,该库安排了其他几个任务。如果在应用程序任务运行时降低线程优先级,那么如果池没有产生许多线程,则可能会出现等待低优先级任务完成的lib中的正常优先级任务,但是如果系统的其余部分有许多希望运行的普通优先级线程,则可能不会给予低优先级线程太多的CPU时间。增加池线程的数量将缓解这种情况,但代价是在堆栈上浪费更多的内存,并且在上下文切换上花费更多的CPU时间。

0

这是不鼓励的,特别是如果你要提高优先级,因为它可能会影响整个系统的性能。

总体而言,线程优先级是一个复杂的话题。例如,Windows 有两个相关描述符:线程优先级和进程优先级,都从最低的空闲到最高的时间关键性。当您启动新进程时,它将设置为默认值,即中间范围(具有普通线程优先级的普通进程优先级)。

此外,线程优先级是相对的,这意味着即使在繁忙的系统中将线程优先级设置为最高也不能保证它将以“实时”运行。DotNET 不保证这一点,Windows 也不保证。从这一点可以看出,最好让线程池自己去操作,因为99.9%的情况下,它知道得最好:)

尽管如此,在涉及长时间计算的任务中,降低线程优先级是可以的。这不会影响其他进程。

然而,仅应为需要快速反应且执行时间短的任务而增加优先级,因为这可能会对其他进程产生负面影响。


0

如果你真的想改变它,你肯定可以,但是考虑到线程池中的线程是会被重复使用的,下一个运行的代码可能不希望发生这种改变。


1
我应该提到,当你完成线程或任务时,可以恢复原始优先级... - Glenn Slayden
@Glenn:它就是不打算被更改。如果你这样做,CLR警察不会戴上手铐抓你,但这被认为是一种不好的实践,因为线程池不应该有身份。 - user541686

0

如果你要更改任何内容,请使用try/finally确保你离开时与当初相同。


3
不太有帮助。问题是关于为什么或为什么不的。 - Adam Spicer
@Adam - 我不同意这是无用的。虽然其他回答已经涵盖了原因,但如果经过权衡后有人决定继续执行此操作,那么在finally块中恢复到原始状态以便即使发生异常也会执行是一个好的实践,并且可能会被遗忘。 - Joe
1
看看Michael的回答。它指出了一个很棒的线程池资源,其中提到“您可以自由更改池化线程的优先级 - 当释放回池时,它将恢复正常。” - Adam Spicer
@Adam - 很有趣,但在依赖它之前最好能看到官方文件确认。否则,您必须将其视为不能依赖的实现细节。那么线程的其他属性如文化呢?我仍然认为,在没有官方文件说明不需要这样做的情况下,在finally块中恢复状态是一个好习惯。 - Joe
我同意这是无益的,因为下面的答案 - 即使您进行修改,它也不会保留。一旦它返回到线程池,它就会被重置。似乎线程池会保护自己免受这种干涉,因此try/finally只是增加了性能负担,没有改进结果。 - Chris Moschini
1
@Joe:在.NET TaskFactory任务的上下文中,try/finally不是正确的管理方式。(直接管理自己的线程是另一回事。)想象一下,你的任务调用一个库函数,在某个地方使用了Wait()Sleep()...当执行返回到你的任务代码时,它可能是来自线程池完全不同的线程,而你原来的线程正在被另一个任务使用。例如,[https://dev59.com/XWct5IYBdhLWcg3wPLCB#12246045] - AlwaysLearning

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接