【问题标题】:Thread with the lowest priority is invoked more times优先级最低的线程被调用更多次
【发布时间】:2014-10-31 07:52:34
【问题描述】:

以下代码 5 个具有不同优先级的线程正在竞争访问具有 8 个内核的 CPU(Mac OS X 10.8.5,Mono)。每个线程都会增加它的计数器。

using System;
using System.Threading;

      class PriorityTesting 
     { 
       static long[] counts; 
       static bool finish;

       static void ThreadFunc(object iThread) 
       { 
         while(true) 
         { 
           if(finish) 
              break; 
           counts[(int)iThread]++; 
         } 
       }

       static void Main() 
       { 
         counts = new long[5]; 
         Thread[] t = new Thread[5]; 
         for(int i=0; i<t.Length; i++)  
         { 
           t[i] = new Thread(ThreadFunc); 
           t[i].Priority = (ThreadPriority)i; 
         } 
         // Запускаем потоки 
         for(int i=0; i<t.Length; i++) 
           t[i].Start(i);

         // Даём потокам возможность поработать 10 c 
         Thread.Sleep(10000);

         // Сигнал о завершении 
         finish = true;

         // Ожидаем завершения всех потоков 
         for(int i=0; i<t.Length; i++) 
           t[i].Join(); 
         // Вывод результатов 
         for(int i=0; i<t.Length; i++) 
           Console.WriteLine("Thread with priority {0, 15}, Counts: {1}", (ThreadPriority)i, counts[i]); 
       }   
     }

编译:

$ mcs PriorityTesting.cs
$ mono PriorityTesting.exe

输出:

Thread with priority          Lowest, Counts: 178544880
Thread with priority     BelowNormal, Counts: 167783608
Thread with priority          Normal, Counts: 160593225
Thread with priority     AboveNormal, Counts: 79123315
Thread with priority         Highest, Counts: 81623159

为什么优先级最低的线程比优先级最高的线程调用次数多?

UPD:

2 核 CPU 上的相同代码给出(Windows、.NET):

 Thread with priority         Lowest, Counts:    7608195 
 Thread with priority    BelowNormal, Counts:   10457706 
 Thread with priority         Normal, Counts:   17852629 
 Thread with priority    AboveNormal, Counts:  297729812 
 Thread with priority        Highest, Counts:  302506232

为什么不一样?

【问题讨论】:

  • 如果您修复代码以便将finish 变量标记为volatile(应该如此),您是否注意到不同的行为?当然,我正在抓紧救命稻草,但它可能会有所不同,具体取决于您在什么硬件上运行它。
  • 我会以某种方式更改 ThreadFunc,以便更好地切换线程,如 Thread.Yield 或 Console.WriteLine... 也许是因为循环太快,结果会失真。
  • 你测试过哪个操作系统?
  • 在以不同方式运行 Win7x64-I7-950 (debug/release/cmd-line/msvsdev) 后,结果如下;此代码也可能在 MS .Net 运行时产生意外结果(请参阅下面的答案以了解可能的原因)。

标签: c# multithreading mono task-parallel-library thread-priority


【解决方案1】:

Priority 支持未在 Mono 中实现,因此您看到的有关 Lowest 被多次调用的行为可能只是运气。

在 github 中有来自实现此属性的贡献者的拉取请求,因此您可能希望与他一起要求对其进行审核。

更新:这个答案可能已经过时了。随着 Mono 的快速发展,您最好重新测试您的程序,也许现在优先级可以工作。

【讨论】:

  • 这似乎是正确的。刚刚测试了代码并在带有 MS.Net 运行时的 win7 机器上产生了预期的结果(低优先级最低计数,高优先级最高计数)。
  • @MarvinSmit 您是在发布版还是调试版中运行的?在为发布而编译时,我仍然可以获得最低优先级的非常高的价值。其他四个很接近,但仍然不是完美的排序。 CPU 能力也是一个因素(核心/逻辑线程数)。
  • 更新:经过更多测试,OP的代码在Win7(MS版)上使用.Net也无法正常工作
【解决方案2】:

发生这种情况的原因有很多。

有一种叫做“Priority Boost”的东西,它就是其中之一,操作系统会出于各种原因提高优先级(暂时提高线程的优先级)示例:一个线程刚刚收到一个事件信号,它是 UI为消息泵送的线程并且在其队列中收到了一条新消息,另一个原因可能是饥饿

饥饿可能就是这种情况。当有更高优先级的线程准备好或正在运行时,您的低优先级线程将没有机会执行。如果一个线程长时间处于饥饿状态,操作系统会为处于饥饿状态的线程提高优先级,从而立即抢占正在运行的线程并获得执行机会,从而抵消饥饿时间。

我在 windows 中测试过,结果几乎一样,这里是解释。出于怜悯,操作系统为饥饿的线程提供了提升。我想类似的东西也存在于其他操作系统中。

Windows 有一个称为平衡集管理器的服务。它运行 在系统线程上异步寻找饥饿线程;这些 是已经等待以就绪状态运行 4 的线程 秒或更长时间。如果它找到一个,它会给线程一个 临时优先级提升。它总是提高饥饿线程的优先级 到 1 5 级,无论其当前值如何。这样做是为了打击 饥饿,例如,当许多更高优先级的线程 不断运行,使得较低优先级的线程永远没有机会 执行。

引用本书Concurrent Programming on Windows-Joe Duffy

更新:

至于您在具有 2 核的 Windows 操作系统中的更新结果,是的结果并不奇怪没有足够的 cpu 来运行处于就绪状态的线程,因此操作系统必须等到其他线程完成它们 线程量子。有足够多的高优先级线程而不是低优先级线程,因此显然操作系统会优先考虑高优先级线程。

对于 8 核的结果:如前所述,mono 不支持优先级,您首先启动了较低优先级的线程,并且您有足够的处理器来运行可运行线程。所以这里不需要抢占低优先级的线程。所以操作系统允许线程在不干扰(抢占)的情况下运行。尝试使用比您拥有的处理器数量更多的线程进行测试。这就是优先级真正发挥作用的地方。

【讨论】:

  • @downvoter 如果你能给出理由,我很感激你的反对意见?
【解决方案3】:

您首先启动最低优先级的线程,然后升级到最高优先级。这使低优先级线程有时间运行,而高优先级线程开始运行(启动线程不是一个简单的操作)。

创建另一个布尔标志来调节线程函数中的计数,以便所有线程在任何线程开始计数之前都已启动:

   static long[] counts; 
   static bool finish;
   static bool count; // add this

   static void ThreadFunc(object iThread) 
   { 
     while(true) 
     { 
       if(finish) 
          break;

       if( count ) // only count when ready
           counts[(int)iThread]++; 
     } 
   }

Main:

   // After your loop to start the threads
   // set the count flag to start counting
   count = true;
   // Даём потокам возможность поработать 10 c 
   Thread.Sleep(10000);

【讨论】:

  • 缺少一个 volatile 的标志,这可能会导致非常令人惊讶的结果。
【解决方案4】:

首先 - 不要乱用线程优先级。或进程优先级。您不会提供任何帮助,而且您很可能会在进行过程中导致许多死锁和其他同步问题,这将是调试的一大乐趣。相信操作系统的线程调度器会完成它的工作 - 它实际上非常擅长!

其次,Mono 不支持优先级。

第三,您可能希望给线程足够的工作以真正给它们切换的理由。八核 CPU 上的五个线程根本不会获得足够的争用来保证任何线程切换,而且它们可能都以相同的速度运行。要试验线程优先级,您需要比 CPU 内核更多的线程来工作。是的,超线程内核也很重要。

不要只是做一个“愚蠢”的计数器——在每个计数器步骤中为 CPU 添加一些愚蠢的工作,比如Thread.SpinWait(100000)。这会给 CPU 带来一些愚蠢的压力,这将使您的计数更加有趣和可比较。

我从未见过更改线程优先级的好案例。总会有更好的解决方案,而您很可能正在尝试解决一个不存在或可以以更好的方式解决的问题。 Jeff Atwood 的http://blog.codinghorror.com/thread-priorities-are-evil/ 是一个很好的关于优先级问题的入门参考。

请注意,像 Task 这样的高级多线程/并行结构没有任何方式建议优先级或类似的东西 - 这是有充分理由的。

保重:)

【讨论】:

    猜你喜欢
    • 2014-11-26
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-27
    • 2012-02-01
    相关资源
    最近更新 更多