注:在Linux系统下实现。

1. 首先我们看看程序运行的结果

记录cpu占用进程:控制进程的CPU使用率(1)

上图是开启两个线程CPU占用率的一个曲线,这是一个余弦曲线。

2. 如何计算CPU的使用率

计算CPU的使用率我们都是计算一段时间内的CPU使用率,不是计算某个时刻的计算机使用率。

查看/proc/[pid]/stat这个文件,其中[pid]是需要查看的进程的id,我们可以通过ps命令查看。我们打开stat文件,内容大概长下面这个样子,每一项用空格隔开,每个进程项目相同,各个项目的数字不同。

$ cat /proc/16743/stat 16743 (fake_process) S 505 16743 505 34819 16743 1077952512 160 0 0 0 135317 814 0 0 20 0 3 0 802402 22978560 403 18446744073709551615 94252505616384 94252505629301 140722538756160 0 0 0 0 0 0 0 0 0 17 5 0 0 0 0 0 94252505648072 94252505649184 94252509548544 140722538758747 140722538758762 140722538758762 140722538762217 0

文档中具体每一项的含义可以通过以下命令查询(鉴于大家都用手机看帖子,把不必要的内容删了)

$ man proc .... # 省略/proc/[pid]目录下其它文件说明,我们直接看stat文件 /proc/[pid]/stat ........... (14) utime %lu Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This in‐cludes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations. (15) stime %lu Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). ......... #省略其它说明

man proc中详细地说明了每一项的含义。对于CPU的使用率我们主要关心第14项和第15项。utime表示进程从开始运行到目前占用的用户态时间,后面跟的%lu表示这是一个unsigned long类型的字段;stime表示进程从开始运行到目前占用的内核态时间,后面跟的%lu表示这是一个unsigned long类型的字段。这里需要特别注意的是占用时间单位是clock ticks

clock ticks,使用sysconf(_SC_CLK_TCK)表示一秒内有多少个clock ticks,一般是100,即1秒被分成100个clock ticks,1个clock ticks表示1/100秒。也就是说我们可以用下面公式表示CPU一段时间的使用率

long last = utime stime; // 间隔一段时间:这里用一秒, 可以任意时间间隔 /* ...... */ int period = 1; // 1秒 long curr = utime stime; // 这里表示重新从/proc/[pid]/stat里读取utime和stime double ratio = (curr - last) * 1.0 / (period * sysconf(_SC_CLK_TCK)); /* 这里乘1.0是为了获取浮点数*/

3. 定制CPU的运行效率

比如我们需要让进程在一段时间间隔(我们设为P)内CPU使用率为50%,则我们让其中P/2的时间CPU满负荷运行,让后P/2时间进程让出CPU,则整个时间段内,进程的CPU使用率是50%,如图:

记录cpu占用进程:控制进程的CPU使用率(2)

在上图中方案1,让CPU前50%的时间运行后50%时间让出CPU,这样导致进程占用CPU的时间不均匀,这种方式运行出来的CPU占用率曲线出现突刺的概率比较大,曲线不够平滑。方案2中,把一段时间平均分成N份,随机让其中n份运行,这样CPU占用率为n/N,曲线效果比较好。在要求不高的情况下,使用第一种方案比较简单、容易实现。

4. show me the code-代码实现

实现思路,首先选择曲线:

记录cpu占用进程:控制进程的CPU使用率(3)

一个线程根据曲线公式计算每个时刻(为了方便说明和计算,我们以1秒为一个计算周期)的y的值,y的值即CPU的占用率;然后把1秒平分成100份,然后从1到100,根据某种方式选择其中的y份打开运行开关,其中的100-y份关闭运行开关。比如可以写出如下伪代码,详细代码最后贴出:

void load(int percent) { // 确保 0 <= percent <= 100 percent = std::max(0, percent); percent = std::min(100, percent); for (size_t i = 0; i < 100; i ) { bool busy = false; if (i < percent) busy = true; // g_busy是线程是否运行的开关 g_busy = busy; // 把一秒分成100份,休眠10ms让其它线程运行 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }

其它的一个或者多个线程根据运行开关的开闭来确定自己是否运行。

std::unique_lock<std::mutex> lock(_m); while (!g_busy) { g_cond.wait(lock); } busy(g_cycles); // 运行

现在我们的重点就要确定我们的曲线和busy函数

代码中我们实现让CPU使用率是一条余弦曲线,当然自己可以按这个思路实现其它曲线。

余弦曲线

记录cpu占用进程:控制进程的CPU使用率(4)

y的取值范围为[-1, 1], x为(-无穷, 无穷) ,而且一个曲线周期是2 * PI。我们用其中x代表我们的时间,y代表CPU的占用率。那么x >= 0, 0 <= y < 100 (100%),且我们可以定义自己的曲线周期。最后的代码是这样的:

for (size_t i = 0; i < 200; i ) { // 以200为周期 // i * 3.141592 / 100 的范围为[0, 2*PI],假设闭区间 // (1.0 cos(i * 3.141592 / 100)) 的范围为[0,2] // (1.0 cos(i * 3.141592 / 100)) / 2 的范围为[0,1] // (1.0 cos(i * 3.141592 / 100)) / 2 * max_percent 为[0, max_percent], max_percent为我们设置的最大CPU占用率 // 最后加0.5做精度损失的修正,同时防止出现负数 int percent = static_cast<int>((1.0 cos(i * 3.141592 / 100)) / 2 * g_percent 0.5); load(percent); }

在excel里画出来的曲线是这样的:

记录cpu占用进程:控制进程的CPU使用率(5)

确定了我们的曲线,然后就是确定我们的运行函数了,运行函数最好最大程度的使用我们的CPU,我们选择开平方根函数,也可以选择其它CPU密集的函数。

for (size_t i = 0; i < cycles; i ) { result = sqrt(i) * sqrt(i 1); }

这里有一个难点就是确定cycles的大小,让我们的运行函数尽量运行约10ms时间。

以下是完整的代码,以及在实现过程中遇到的坑。

#include <cmath> #include <algorithm> #include <mutex> #include <condition_variable> #include <vector> #include <thread> #include <atomic> #include <iostream> #include <unistd.h> #include <chrono> std::mutex _m; std::condition_variable g_cond; bool g_busy = false; std::atomic<int> g_done; int g_cycles = 0; int g_percent = 82; void load(int percent) { percent = std::max(0, percent); percent = std::min(100, percent); // Bresenham's line algorithm !!! int err = 2 * percent - 100; int count = 0; for (size_t i = 0; i < 100; i ) { bool busy = false; if (err > 0) { busy = true; err = 2 * (percent - 100); count; } else { err = 2 * percent; } { std::unique_lock<std::mutex> lock(_m); g_busy = busy; g_cond.notify_all(); } // 把一秒分成100份 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } double timedifference(std::chrono::steady_clock::time_point start, std::chrono::steady_clock::time_point end) { std::chrono::duration<double> d = std::chrono::duration_cast<std::chrono::duration<double>>(end - start); return d.count(); } double busy(int cycles) { double result = 0; for (size_t i = 0; i < cycles; i ) { result = sqrt(i) * sqrt(i 1); } return result; } void func() { while (g_done.load() == 0) { { // 这里主要是要释放锁,防止某个线程一直占用锁,而主线程无法修改busy std::unique_lock<std::mutex> lock(_m); while (!g_busy) { g_cond.wait(lock); } } busy(g_cycles); } } void cosine() { while (true) { for (size_t i = 0; i < 200; i ) { // 以200为周期 int percent = static_cast<int>((1.0 cos(i * 3.141592 / 100)) / 2 * g_percent 0.5); load(percent); } } } double get_seconds(int cycles) { std::chrono::steady_clock::time_point tp = std::chrono::steady_clock::now(); busy(cycles); return timedifference(tp, std::chrono::steady_clock::now()); } void find_cycles() { g_cycles = 800; while (get_seconds(g_cycles) < 0.01) { // 找到运行约10毫秒的cycle数 g_cycles = g_cycles g_cycles / 4; } } int main(int argc, char const *argv[]) { std::vector<std::thread> ths; for (size_t i = 0; i < 2; i ) { ths.emplace_back(func); } find_cycles(); std::cout << "pid : " << ::getpid() << std::endl; cosine(); g_done = 1; { std::unique_lock<std::mutex> lock(_m); g_busy = true; g_cond.notify_all(); } for (auto& t : ths) { t.join(); } return 0; }

代码注意点提示:

  1. find_cycles函数,这个函数是为了找到使busy运行大约10ms时,需要循环的次数。
  2. func函数里,锁一定要在busy运行之前释放,如果不释放;可能导致本线程一直获取到锁;即计算曲线函数的线程一直获取不到锁(即我们平时说的线程被饿死),不能改变busy的状态,运行线程一直运行,导致CPU一直100%运行。
  3. load函数里用了布雷森汉姆直线演算法来开闭运行开关。

这一节写了进程怎么抛出任意CPU占用率的方法,下一节看看怎么把我们的进程参数发送到浏览器客户端展示,倒是我们会用到boost的beast库和asio库。

,