Go語言控制CPU佔用率呈正弦曲線|Go主題月

題目來源

《編程之美》的第一道題目,原文中給出了C語言的解法、相關函數接口與工具。思考如何使用Go來實現這一目標?git

寫一個程序,讓用戶來決定Windows任務管理器的CPU佔用率。能夠實現下面三種狀況:github

  1. CPU的佔用率固定在50%,爲一條直線;
  2. CPU的佔用率爲條直線,可是具體佔用率由命令行參數以爲(1~100);
  3. CPU的佔用率狀態是一個正弦曲線
  4. 若是你的電腦是雙核的,那麼你的程序會有什麼樣的結果爲何?

解決思路:

思考:如何使CPU的佔用率爲50%呢? 咱們認爲大致上有兩種思路:編程

  1. CPU一直運行,但速率/頻率是全速運行的50%。
  2. CPU按時間來劃分,50%的時間全速運行,50%的時間不運行。

爲何放棄思路1:調節CPU頻率

​ (1) CPU的頻率是與CPU的時鐘週期有關,須要獲取更多的硬件支持,而非軟件角度markdown

​ (2) 不必定具備普適性,好比調節Intel CPU與其餘品牌的CPU頻率方法不一樣,不一樣系列CPU支持也不一樣,指令集也不一樣。svn

​ (3) 控制CPU頻率須要程序較高的執行權限,好比root權限函數

​ (4) 固然並不能說這種思路徹底不可行,Linux有 cpufrequtils 支持設置CPU的運行模式,其中有一種模式「userspace」 , 即用戶自定義模式,供用戶應用程序調節CPU運行頻率,對這一思路感興趣的同窗能夠嘗試一下。工具

​ (5) 最主要的緣由是,當咱們限制CPU頻率爲全速的50%時,CPU利用率會顯示多少呢?也許此時,50%的頻率又變成了CPU利用率的分母。測試

CPU利用率的計算公式:flex

RealTimeCPULoad=1-(RTCPUPerformance/CPUPerformanceBase)*100%。url

但如同不該該拿筷子喝粥同樣,人不該該陷入一種思路而不自拔。

思路2:控制運行與休眠時間比例

若是在必定時間範圍內,控制程序運行與休眠時間的比例,也就控制了CPU的利用率。

思路2的方式是可行的,且在短期內是能夠經過代碼實現的。

好比咱們在1秒內,讓程序執行500毫秒,休眠500毫秒,則能夠認爲CPU的利用率爲50%。

實現思路2,咱們須要理清如下問題:

  1. CPU利用率的統計週期是多久?是否有函數或方法可獲取到CPU的週期數?
  2. 如何讓CPU 「忙」 起來?
  3. 如何讓CPU 「閒」 起來?
  4. 多核心的CPU利用率統計規則?
  5. 如何表達正弦曲線的X軸:時間?

CPU利用率的統計週期

觀察任務管理器,大體可推斷出統計週期爲1秒。實際在程序中須要經過不一樣的參數值來進行測試。

讓CPU 「忙」 起來

根據《編程之美》的敘述,當程序執行運算,作一些複雜操做,死循環等可以讓CPU佔用率上升。應該採用哪一種方式呢?

根據解決思路,讓CPU忙起來的時間段內,須要讓CPU短暫利用率爲100%。所以咱們須要考察與測試,哪一種方式可以讓CPU的佔用率達到100%。 感興趣的同窗可在稍後的程序中測試如下幾種運算是否有差異: (1) 計算MD5值 (2) 執行+1操做 (3) 執行空循環

讓CPU 「閒」 下來

相似C語言中的Sleep函數,Go中也有time.Sleep(time.Duration)函數,形參time.Duration表示休眠的時間段。

多核心的CPU利用率統計規則

假如CPU有兩個核心,一個核心運行狀態爲100%利用率,另外一個核心爲空閒狀態,CPU的利用率會使什麼狀況呢?

首先提出假設:Windows任務處理器的CPU佔用率爲多個核心的總佔用率,即:N個核的CPU的佔用率公式爲:

CPU佔用率 = (Z_1 + Z_2 + ... + Z_N) / N
複製代碼

其中,Z_i表示第i個核的利用率。

在後續程序中,可經過配置不一樣的協程goroutine數目來測試如何達到理想目標。參考設置的值有1,2(內核數),4(邏輯處理器數量)。

如何表達X軸:時間

e61190ef76c6a7ef00b9298bf7faaf51f2de6684.jpg

image.png

如圖,正弦曲線與CPU佔用率窗口如圖所示。

正弦函數sin(X)的取值範圍爲區間[-1, 1],CPU利用率區間爲[0, 100%],作一個sin(X) -> CPU利用率的映射,很容易得出:

CPU利用率 = (sin(x) + 1) / 2, 其中x爲程序運行時間
複製代碼

任務管理器展現CPU佔用率時間窗口爲60秒,正弦函數一個週期爲區間[0, 2π],作 [0, 60] -> [0, 2π] 的映射;即隨着時間從0 ~ 60秒變化,x的變化爲0 ~ 2π;即經過設置x的每秒步長爲2π/60 ≈ 0.1,則60秒的窗口可繪製一個完整的正弦曲線。 即程序運行x秒時:

CPU利用率 = ( sin(0.1x) + 1 )/ 2,其中x爲程序運行秒數
複製代碼

實現代碼

如下代碼實現了CPU利用率呈現正弦函數:

package main

import (
	"fmt"
	"math"
	"time"
)

func main() {
    // 經過設置不一樣的coreNum測試多核心CPU利用率
	coreNum := 4
	for i := 0; i < coreNum; i++ {
		go task(i)
	}
    // 經過等待輸入實現main-goroutine不會終止
	a := ""
	fmt.Scan(&a)

}

func task(id int) {
	var j float64 = 0.0
    // 經過設置step來決定一個60秒的統計窗口展現幾個正弦函數週期
    var step float64 = 0.1
	for j = 0.0; j < 8*2*math.Pi; j += step {
		compute(1000.0, math.Sin(j)/2.0+0.5, id)
	}
}

/** * t 一個總的CPU利用率的統計週期,1000毫秒,感興趣的能夠測試一下時間段小於1000毫秒與大於1000毫秒的狀況下曲線如何 * percent [0, 1], CPU利用率百分比 */
func compute(t, percent float64, id int) {
	// t 總時間,轉換爲納秒
	var r int64 = 1000 * 1000
	totalNanoTime := t * (float64)(r)               // 納秒
	runtime := totalNanoTime * percent              // 納秒
	sleeptime := totalNanoTime - runtime            // 納秒
	starttime := time.Now().UnixNano()              // 當前的納秒數
	d := time.Duration(sleeptime) * time.Nanosecond // 休眠時間
	fmt.Println("id:", id, ", totaltime = ", t, ", runtime = ", runtime, ", sleeptime = ", sleeptime, " sleep-duration=", d, ", nano = ", time.Now().UnixNano())
	for float64(time.Now().UnixNano())-float64(starttime) < runtime {
		// 此處易出錯:只能用UnixNano而不能使用Now().Unix()
		// 由於Unix()的單位是秒,而整個運行週期
	}
	time.Sleep(d)
}

複製代碼

思考問題

留幾個問題給小夥伴們思考:

  1. compute函數中爲何要轉換爲納秒?

答↑:由於一個週期是1000毫秒,也就是1秒,你不能用秒級單位來執行循環。固然,也能夠用Millisecond或者Microsecond,可是不能用秒,而我只找到了兩個熟悉的函數,time.Now().UnixNano(),time.Now().Unix(),因此只能選用第一個納秒,小夥們能夠能夠嘗試一下用秒回有什麼現象

  1. 多個邏輯處理器,運行一段時間後,各個協程之間的執行進度不一樣了,這是爲何?如何保持同步? 答↑:進度不一樣,1是可能受其餘程序影響,2是各個核心的速度略有差別。能夠經過在compute中傳遞從程序開始運行到當前時刻的時間差,而非傳遞1000毫秒來實現同步;或者使用其餘同步方法

  2. 讓部分核心始終「閒」,讓部分核心始終「忙」會有什麼現象?循環中執行各類不一樣運算有何種差異?

答↑:自行測試吧

  1. 可否消除其餘程序佔用CPU的影響呢?

答↑:我不知道。 嘗試獲取cpu利用率:github/shirou/gopsutil/cpu,並在每次進入compute函數時經過在原百分比percent中移除其餘程序執行所佔百分比,可是後來發現,受限於gopsutil/cpu獲取CPU利用率的方法,cpu.Percent(duration time.Duration, percpu bool)獲取cpu利用率方法自己就須要1秒,因此你會獲得一個十分跳躍的曲線。我暫時沒有找到其餘方法。歡迎知道的小夥伴在評論去留言指教

相關文章
相關標籤/搜索