《编程之美》的第一道题目,原文中给出了C语言的解法、相关函数接口与工具。思考如何使用Go来实现这一目标?git
写一个程序,让用户来决定Windows任务管理器的CPU占用率。能够实现下面三种状况:github
- CPU的占用率固定在50%,为一条直线;
- CPU的占用率为条直线,可是具体占用率由命令行参数以为(1~100);
- CPU的占用率状态是一个正弦曲线
- 若是你的电脑是双核的,那么你的程序会有什么样的结果为何?
思考:如何使CPU的占用率为50%呢? 咱们认为大致上有两种思路:编程
(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
但如同不该该拿筷子喝粥同样,人不该该陷入一种思路而不自拔。
若是在必定时间范围内,控制程序运行与休眠时间的比例,也就控制了CPU的利用率。
思路2的方式是可行的,且在短期内是能够经过代码实现的。
好比咱们在1秒内,让程序执行500毫秒,休眠500毫秒,则能够认为CPU的利用率为50%。
实现思路2,咱们须要理清如下问题:
观察任务管理器,大体可推断出统计周期为1秒。实际在程序中须要经过不一样的参数值来进行测试。
根据《编程之美》的叙述,当程序执行运算,作一些复杂操做,死循环等可以让CPU占用率上升。应该采用哪一种方式呢?
根据解决思路,让CPU忙起来的时间段内,须要让CPU短暂利用率为100%。所以咱们须要考察与测试,哪一种方式可以让CPU的占用率达到100%。 感兴趣的同窗可在稍后的程序中测试如下几种运算是否有差异: (1) 计算MD5值 (2) 执行+1操做 (3) 执行空循环
相似C语言中的Sleep函数,Go中也有time.Sleep(time.Duration)函数,形参time.Duration表示休眠的时间段。
假如CPU有两个核心,一个核心运行状态为100%利用率,另外一个核心为空闲状态,CPU的利用率会使什么状况呢?
首先提出假设:Windows任务处理器的CPU占用率为多个核心的总占用率,即:N个核的CPU的占用率公式为:
CPU占用率 = (Z_1 + Z_2 + ... + Z_N) / N
复制代码
其中,Z_i表示第i个核的利用率。
在后续程序中,可经过配置不一样的协程goroutine数目来测试如何达到理想目标。参考设置的值有1,2(内核数),4(逻辑处理器数量)。
如图,正弦曲线与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)
}
复制代码
留几个问题给小伙伴们思考:
compute
函数中为何要转换为纳秒?答↑:由于一个周期是1000毫秒,也就是1秒,你不能用秒级单位来执行循环。固然,也能够用Millisecond或者Microsecond,可是不能用秒,而我只找到了两个熟悉的函数,time.Now().UnixNano(),time.Now().Unix(),因此只能选用第一个纳秒,小伙们能够能够尝试一下用秒回有什么现象
多个逻辑处理器,运行一段时间后,各个协程之间的执行进度不一样了,这是为何?如何保持同步? 答↑:进度不一样,1是可能受其余程序影响,2是各个核心的速度略有差别。能够经过在compute
中传递从程序开始运行到当前时刻的时间差,而非传递1000毫秒来实现同步;或者使用其余同步方法
让部分核心始终“闲”,让部分核心始终“忙”会有什么现象?循环中执行各类不一样运算有何种差异?
答↑:自行测试吧
答↑:我不知道。 尝试获取cpu利用率:github/shirou/gopsutil/cpu
,并在每次进入compute
函数时经过在原百分比percent
中移除其余程序执行所占百分比,可是后来发现,受限于gopsutil/cpu获取CPU利用率的方法,cpu.Percent(duration time.Duration, percpu bool)
获取cpu利用率方法自己就须要1秒,因此你会获得一个十分跳跃的曲线。我暂时没有找到其余方法。欢迎知道的小伙伴在评论去留言指教