怎樣讓Windows任務管理器CPU占用率呈現正玄曲線(解釋+C#實現)


最近看CLR via C#的線程一部分,對線程的理解有了很大的提高。於是我突然想起了大學時在光谷書城看到的一本書,講的是微軟的面試,翻了幾頁就發現了作者在說這個問題。當時我感覺這個作者很牛B,因為對當時的水平還比較菜。后來就淡忘了。(當然我也沒買那本書,沒想到今后會求職)

今天突然想起來這個問題。小試一下,居然成功了。我在這里斗膽向各位介紹一下背后的原理並附上代碼,這種程序沒有什么實際意義,純粹一種加深對多線程理解的練習。

有圖有真像:

其實我的第一次嘗試是這樣的:

如果有不懂的,先看我下面的解釋,然后我再解釋上面兩幅圖代表的意思(先申明機器是雙核的)。

1. CPU占用率是什么?

首先我們得知道CPU占用率是什么,也就是任務管理器上的數據代表什么意思。

一個CPU占用率就是一段時間內CPU真正執行指令的時間占這段時間的比率。比如你的CPU在1000毫秒之內,有300毫秒執行指令,700毫秒閑着,那么CPU占用率就是30%。所以我們應該知道,CPU占用率是一段時間的統計值,而不是一個某一個時刻的瞬時值。據肉眼觀察,我發現Windows任務管理器大概一秒一個占用率數據。

另一個重要的概念就是CPU空轉和空閑的區分。CPU空轉是占用着的,也就是CPU在執行一些沒用的空指令,就像這樣

1 while(true)
2 {}

CPU這時候就不斷的運行,不僅耗電,而且純屬浪費。而CPU空閑則指CPU沒有執行什么指令,一般是操作系統來控制的。比如

1 Thread.Sleep(500);

就讓當前的線程停止執行500毫秒的時間,Windows調度程序就把線程掛起,CPU用來做其他的事情或者什么都不干,不費電。這段時間是不算在占用里的。

2. 怎么實現?

既然我們知道了什么是占用率,那么我們就知道該怎么做了。如果我們希望一段時間內CPU占用率為30%,那么我們要這樣做:

1> 首先隨便執行點指令

2> 如果當前的所消耗的時間不到指定時間的30%,返回1

3> 剩下的時間Sleep

這樣我們就能保證這段時間內,CPU占用率是30%。但是這段時間一定要非常小,至少要小於Windows任務管理器的采樣時間間隔。越小就越精准,但太小了也不行,沒法精確控制時間。所以建議時間為500-1000毫秒。

既然我們知道了在一個很短的時間內怎么控制其CPU占用率,那么剩下的就是怎么產生一個使用率了。為了產生正弦使用率,我們要使用Math.Sin()函數(廢話)。但是Sin的值域是[-1,1],而占用率是[0,1],所以我們要把Sin的值域轉換為占用率,即sin(x)/2+0.5。

接下來就是不斷地從0到2*PI之間循環,中間調用控制CPU占用率的函數即可。

3. 問題

如果你是按照以上來實現的話,有可能成功,但是在多核的機器上效果就是我一開始實現的效果(圖2)。系統把一個線程分在兩個CPU上執行,所以只能看到大致的形狀。因為有兩個CPU,所以使用率最大是50%。

為了解決多核的問題,我們要獲得當前機器的CPU數量,然后在安排同樣數量的線程去執行循環。最方便的就是使用線程池的QueueUserWorkItem。然后主線程一定要放棄CPU,無窮等待,要不然你的程序一下子就關閉了。因為使用線程池,所以無法單獨設置某個線程為前台還是后台,所以只能讓主線程等了。

如果正確地設置了多線程執行,那么在你的機器上應該能看到如圖1的效果。不完美之處可能由兩個原因導致。首先你的系統有其他程序在執行,會破壞占用率既有形式。所以測試的時候盡量不要開其他程序。另一個原因是我使用了C#,而.Net在垃圾回收時,會使用CPU,也會破壞既有占有率。可以一定程度上避免垃圾回收的影響,但是得不償失,對我們這個程序用處不大,所以直接可以忽略。

注意在Linux下CPU占用率(top)好像和Windows有點區別,Linux下每個CPU都是100%,所以你有兩個核,最大占有率是200%。而Windows是所有的核加在一起是100%。

4. 代碼

 1 using System;
2 using System.Threading;
3 using System.Diagnostics;
4
5 public sealed class Program
6 {
7 public static void Main(string[] args)
8 {
9 Console.WriteLine("Program SIN");
10 Console.WriteLine("Making the Windows Task Manager show Sine Wave Pattern in CPU usage");
11 Console.WriteLine("Now look at your task manager cpu usage!");
12
13 int numOfCPUs = Environment.ProcessorCount;
14 for(int i = 0; i < numOfCPUs; ++i)
15 {
16 ThreadPool.QueueUserWorkItem(SinWave);
17 }
18 Thread.Sleep(Timeout.Infinite);
19 }
20
21
22 private static void SinWave(object dummy)
23 {
24 while(true)
25 {
26 for(double i = 0.0; i < 2 * Math.PI; i += 0.1)
27 {
28 Compute(500, Math.Sin(i)/2.0 + 0.5);
29 }
30 }
31 }
32
33 private static Stopwatch m_sw = new Stopwatch();
34 private static void Compute(long time, double percent)
35 {
36 long runTime = (long)(time * percent);
37 long sleepTime = time - runTime;
38 m_sw.Start();
39 while(m_sw.ElapsedMilliseconds - runTime < 0 )
40 {
41 // Spin the CPU. Just doing nothing is OK
42 }
43 m_sw.Stop();
44 m_sw.Reset();
45 Thread.Sleep((int)sleepTime);
46 }
47 }

以上代碼中Compute方法獲得一個時間參數(時間間隔),和一個執行時間的百分比,不斷空轉CPU測試是否到達指定的時間,到達后就睡覺。注意精確的時間是不需要的,因為Windows任務管理器畫圖的精度也有限。

SineWave函數讓執行它的CPU不斷的以500毫秒間隔進行,執行一會兒,睡一會兒,但是這個比例是由Sin函數控制的。Sin函數的周期由for循環中i的增量來控制,我選擇的是0.1。

最后主函數只負責給每一CPU扔一個SineWave到線程池里,然后自己就睡覺了。這樣所有的CPU都可以執行同樣的代碼。那么你在任務管理器中就看到正弦曲線了。

5. 最后說兩句

其實這種方法可以實現任何単值有界連續函數(還記得高數嗎?)的畫圖。具體就是把那個函數映射到[0,1]之間就行了。

強烈推薦大家看CLR via C#最新版(第二版的中文版就算了),尤其是最后一部分,對線程、並發等問題的闡述非常精辟,那可都是Richter一輩子的精華啊,甚至比在Windows via C/C++上都好。不一定C#或.Net程序員才能受益,所有的程序員都會受益。我最熟悉的還是C++,但是看了CLR via C#,我對Windows本身的理解都有很大提高。只要你理解了,什么問題都可以自己做出來的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM