本文将要提到的线程及其相关内容,均是指 Windows 操做系统中的线程,不涉及其它操做系统。算法
文章索引windows
在进入正文前,有几个知识点须要你们在阅读前有所了解。多线程
优先级调度算法工具
处理器是一个操做系统执行任务的工具,线程是一个操做系统执行任务的基本单位,处理器的数量决定了不可能全部线程都能同时获得执行。这就须要经过某种算法来进行任务高度。而 Windows 是一个抢占式的多任务操做系统,咱们来看下维基百科对于抢占式的定义:oop
In computing, preemption is the act of temporarily interrupting a task being carried out by a computer system, without requiring its cooperation, and with the intention of resuming the task at a later time. Such a change is known as a context switch. It is normally carried out by a privileged task or part of the system known as a preemptive scheduler, which has the power to preempt, or interrupt, and later resume, other tasks in the system.post
--- Preemption (computing)测试
上面这段英文意味着在抢占式的操做系统中,执行任务的多个线程之间会由于某种因素互相争抢在处理器上运行的机会。而这种因素就是标题所说的 “优先级”。优化
线程是根据其优先级而调度执行的。 即便线程正在运行时中执行,全部线程都是由操做系统分配处理器时间片的。 用于肯定线程执行顺序的调度算法的详细状况随每一个操做系统的不一样而不一样。ui
在某些操做系统下,具备最高优先级(相对于可执行线程而言)的线程通过调度后老是首先运行。 若是具备相同优先级的多个线程均可用,则计划程序将遍历处于该优先级的线程,并为每一个线程提供一个固定的时间片(段)来执行。 只要具备较高优先级的线程能够运行,具备较低优先级的线程就不会执行。 若是在给定的优先级上再也不有可运行的线程,则计划程序将移到下一个较低的优先级并在该优先级上调度线程以执行。 若是此时具备较高优先级的线程能够运行,则具备较低优先级的线程将被抢先,并容许具备较高优先级的线程再次执行。 除此以外,当应用程序的用户界面在前台和后台之间移动时,操做系统还能够动态调整线程优先级。 其余操做系统能够选择使用不一样的调度算法。this
--- 调度线程
上面文字中提到优先级能够由操做系统动态调整。Windows 除了会在先后台切换的时候调整优先级还会为 I/O 操做动态提高优先级,或者使用 “饥渴” 的时间片分配策略来动态调整,若是有线程一直渴望获得时间片可是很长时间都没有得到时间片,Windows 就会临时将这个线程的优先级提升,并一次分配给2倍的时间片来执行,当用完2倍的时间片后,优先级又会恢复到以前的水平。
线程运行状态
一个线程从开始到终止可能会有上述几种状态,这几种状态能够互相转换。(上图中的 “就绪” 指的是 runnable 状态,又称为 “ready to run” 状态,我的感受翻译成 “就绪” 比 “可运行” 要来得明白)。
对上述概念有所了解后,我将正式介绍这三个方法的区别。
该方法是在 .Net 4.0 中推出的新方法,它对应的底层方法是 SwitchToThread。
Yield 的中文翻译为 “放弃”,这里意思是主动放弃当前线程的时间片,并让操做系统调度其它就绪态的线程使用一个时间片。可是若是调用 Yield,只是把当前线程放入到就绪队列中,而不是阻塞队列。若是没有找到其它就绪态的线程,则当前线程继续运行。
Yielding is limited to the processor that is executing the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority. If there are no other threads that are ready to execute on the current processor, the operating system does not yield execution, and this method returns false.
This method is equivalent to using platform invoke to call the native Win32 SwitchToThread function. You should call the Yield method instead of using platform invoke, because platform invoke bypasses any custom threading behavior the host has requested.
优点:比 Thread.Sleep(0) 速度要快,可让低于当前优先级的线程得以运行。能够经过返回值判断是否成功调度了其它线程。
劣势:只能调度同一个处理器的线程,不能调度其它处理器的线程。当没有其它就绪的线程,会一直占用 CPU 时间片,形成 CPU 100%占用率。
Sleep 的意思是告诉操做系统本身要休息 n 毫秒,这段时间就让给另外一个就绪的线程吧。当 n=0 的时候,意思是要放弃本身剩下的时间片,可是仍然是就绪状态,其实意思和 Yield 有点相似。可是 Sleep(0) 只容许那些优先级相等或更高的线程使用当前的CPU,其它线程只能等着挨饿了。若是没有合适的线程,那当前线程会从新使用 CPU 时间片。
If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready.
--- Sleep Function
优点:相比 Yield,能够调度任何处理器的线程使用时间片。
劣势:只能调度优先级相等或更高的线程,意味着优先级低的线程很难得到时间片,极可能永远都调用不到。当没有符合条件的线程,会一直占用 CPU 时间片,形成 CPU 100%占用率。
该方法使用 1 做为参数,这会强制当前线程放弃剩下的时间片,并休息 1 毫秒(由于不是实时操做系统,时间没法保证精确,通常可能会滞后几毫秒或一个时间片)。但所以的好处是,全部其它就绪状态的线程都有机会竞争时间片,而不用在意优先级。
优点:能够调度任何处理器的线程使用时间片。不管有没有符合的线程,都会放弃 CPU 时间,所以 CPU 占用率较低。
劣势:相比 Thread.Sleep(0),由于至少会休息必定时间,因此速度要更慢。
测试环境:Windows 7 32位、VMWare workstation、单处理器单核芯
开发环境:Visual Studio 20十二、控制台项目、Release 编译后将 exe 拷贝到虚拟机后运行
Thread.Yeild
static void Main() { string s = ""; while (true) { s = DateTime.Now.ToString(); //模拟执行某个操做 Thread.Yeild(0); } }
执行效果
Thread.Sleep(0)
static void Main() { string s = ""; while (true) { s = DateTime.Now.ToString(); Thread.Sleep(0); } }
执行效果
Thread.Sleep(1)
static void Main() { string s = ""; while (true) { s = DateTime.Now.ToString(); Thread.Sleep(1); } }
执行效果
经过上述三个实验,很明显说明 Thread.Sleep(1) 对于解决资源 100% 占用是有明显效果的。
个人实验方法很简单,就是经过 while 让该线程不断的执行,为了让你们一目了然两个线程的交替,经过向控制台输出不一样的字符串来验证。
while 语句执行速度至关快,因此必须因此加上一些代码来浪费CPU时间,以避免你们只能看到不断的刷屏。
辅助代码
private static void WasteTime() { // 耗时约 200ms DateTime dt = DateTime.Now; string s = ""; while (DateTime.Now.Subtract(dt).Milliseconds <= 200) { s = DateTime.Now.ToString(); //加上这句,防止被编译器优化 } }
不使用任何方法
让线程们本身争用 CPU 时间。
static void Main(string[] args) { Thread t = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 1 =========="); } }); t.IsBackground = true; Thread t2 = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 2"); } }); t2.IsBackground = true; t2.Start(); t.Start(); Console.ReadKey(); t.Abort(); t2.Abort(); Console.ReadKey(); }
执行效果
Thread.Yeild
修改第一个线程的方法体,加入Thread.Yield。其他代码不变。
Thread t = new Thread(() => { while (true) { WasteTime(); Thread.Yield(); Console.WriteLine("Thread 1 =========="); } });
执行效果
Thread.Sleep(0)
仿照 Thread.Yield,只不过用 Thread.Sleep(0)替换。
执行效果
Thread.Sleep(1)
仿照 Thread.Yield,用 Thread.Sleep(1)替换。
执行效果
从上面的示例看出,未使用 Thread 的这几个方法(Yield,Sleep)的例子,两个同等优先级的线程能够得到差很少彻底同样的CPU时间。
而使用了 Thread 方法的那几个例子或多或少都让另外一个线程多获取了些时间片,可是不一样的方法执行效果差得并很少。这是由于它们所让出的时间片每每都只是几十毫秒的事情,这么短的时间,对于个人测试代码来讲很难顺利扑捉每一个瞬间。
Thread2 要获得更多的时间片
先来调整下代码,修改 Thread 1,让它的优先级变成 “AboveNormal”。
Thread t = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 1 =========="); } }); t.Priority = ThreadPriority.AboveNormal; // 加入这句话 t.IsBackground = true;
不使用任何方法
Thread t = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 1 =========="); } }); t.Priority = ThreadPriority.AboveNormal; t.IsBackground = true; Thread t2 = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 2"); } }); t2.IsBackground = true;
执行效果
从实验中能够代表,低优先级的几乎不多有使用时间片的时候。
Thread.Yeild
修改 Thread 的方法体
Thread t = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 1 ========== {0}",Thread.Yield()); } });
执行效果
使用了 Yeild 以后,很明显低优先级的线程如今也可以得到CPU时间了。
Thread.Sleep(0)
修改方法体
Thread t = new Thread(() => { while (true) { WasteTime(); Console.WriteLine("Thread 1 =========="); Thread.Sleep(0); } });
执行效果
有没有发现低优先级的线程又一次被无情的抛弃了?
Thread.Sleep(1)
仿照 Sleep(0),用 Sleep(1)替换。
执行效果
经过上面的实验,我想你应该已经比较清楚了在不一样优先级的状况下,哪一个方法更适用于去切换线程。
有鉴于 Thread.Sleep(0) 的表现,本人认为应该无情的把它取缔掉。至因而用 Thread.Yeild,仍是 Thread.Sleep(n) (n>0),那就根据实际状况吧。欢迎你们补充~
Consequences of the scheduling algorithm: Sleeping doesn't always help
SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1)