优先级最低的线程被调用的次数更多

本文关键字:调用 线程 优先级 | 更新日期: 2023-09-27 18:28:45

下面的代码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

为什么有区别?

优先级最低的线程被调用的次数更多

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

有一个来自github中实现该属性的贡献者的拉取请求,所以你可能想和他一起要求对其进行审查。

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

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

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

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

我在windows中进行了测试,结果非常一致,下面是解释。操作系统出于怜悯之心给饥饿的线程提供了动力。我想类似的东西也存在于其他操作系统中。

Windows有一项名为"资产负债表管理器"的服务。它在运行在系统线程上异步地寻找饥饿线程;这些是等待在就绪状态下运行4个的线程秒或更长时间。如果它找到一个,它会给线程一个临时优先级提升。它总是提高缺少线程的优先级至级别15,而不管其当前值如何。这样做是为了战斗例如,当许多优先级较高的线程不断运行,使得优先级较低的线程永远不会有机会执行。

引用《Windows并发编程》一书Joe Duffy

更新:

至于您在2核windows操作系统中的更新结果,是的,结果并不奇怪,没有足够的cpu来运行处于就绪状态的线程,因此操作系统必须等待其他线程完成线程量子。有足够多的优先级较高的线程比优先级较低的线程,所以很明显操作系统会优先考虑优先级较高的螺纹。

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

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

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

   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);

首先,不要干扰线程优先级。或处理优先级。你不会有任何帮助,而且在进行过程中很可能会导致许多死锁和其他同步问题,这将是非常有趣的调试。相信操作系统的线程调度程序可以完成它的工作——它实际上非常擅长!

其次,Mono不支持优先级。

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

不要只做一个"愚蠢"的计数器——在每个计数器步骤中为CPU添加一些愚蠢的工作,比如Thread.SpinWait(100000)。这会给CPU带来一些愚蠢的压力,这会让你的计数更有趣,更具可比性。

我从未见过更改线程优先级的好例子。总有更好的解决方案,你很可能试图解决一个不存在的问题,或者可以用更好的方式解决。Jeff Atwood的http://blog.codinghorror.com/thread-priorities-are-evil/.

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

小心:)