吞吐和响应
吞吐:说白了,就是让一个进程不要等待I/O、网络等。一个进程起来从运行状态一直干到僵尸状态,中间不进行任何的等待,这样自然就提高了吞吐率。
响应:系统能够快速的响应用户的操作。当一个用户点击一下鼠标,我当人希望系统能够快速给用户一个反应。这个时间越短越好。
吞吐和响应是一对矛盾。吞吐要求“两耳不闻天下事,一心只读圣贤书”,什么用户体验,总是用户不用来打扰我,我先把自己的事情做好,你再来烦我。但是响应不一样,我的用户,我是老大,我希望我的系统做到有求必应,不管你忙不忙,反正我的需求来了,你就要放下手上的一切事情来响应我的需求,这好像是“老板”行为。
从吞吐和响应的角度,我们可以讲进程分成下面两种:
- I/O 消耗型
- CPU 消耗型
I/O 消耗型没有太多的计算任务,大量的实践用在数据传输,例如,JDBC 这种程序,它不生产数据,只是数据的搬运工。所以这种进程对 CPU 的性能要求不高,它不希望 CPU 有多强悍,它只要求当我需要 CPU 的时候,操作系统能尽快的将 CPU 给到位。
CPU 消耗型,这种进程计算的占比非常大,也就是说这种进程大部分时间里面都在做计算。
那么,有没有一种架构对 I/O 和 CPU 都友好的呢?那当然了,就是 big.LITTLE 架构。请看下面的图片:
![big.LITTLE
在 big.LITTLE 架构里面,有一个非常耗电,非常强悍的 CPU 就像图片里面的大猪,它很能吃,很能干。大猪主要来干那些 CPU 消耗型的进程。
在 big.LITTLE 架构里面,还有一个不怎么强的 CPU ,就像图片里面的小猪,它吃的不多,但是只要 I/O 消耗型的进程要需要它马上到。
Linux 进程的分类
根据 CPU消耗性和 I/O 消耗性的分类角度,Linux 将进程分成了下面两种:
- RT: realtime 实时型进程。这种进程希望能够操作系统第一时间响应它的 CPU 请求。等它用玩了 CPU ,其他的进程才能用 CPU。
- non-RT:非 RT 进程只能等带 RT 使用完 CPU 后,才能使用 CPU。
如上图所示,RT1 的优先级比 RT2 的优先级高,所以在时间片分配上,RT2只能等 RT1 进程跑完之后,再占用 CPU 时间片。非 RT 进程 non-RT2 只能等 RT 任务全部跑完,它才能跑。
我们来进一步讨论一下 RT 和 non-RT 两种进程不同的调度算法。
RT 的调度算法有两种:
- FIFO: RT进程中优先级高先运行完,优先级第低的才能占用时间片。不是先来先得的概念,是优先级高的先得到。
- RR: Round-Robin 轮询的做法,我们都是联合国常任理事国,常任联合国主席的位置大家轮流做。你说公平不公平。
那 non-RT 是如何获得的 CPU 时间片的呢?
这主要看 CFS 算法,CFS 算法是个啥呢?
CFS 的全称是 Completely fair process scheduling ,直译过来是完全公平的算法。完全公平怎样理解呢?人人有饭吃的大同社会。我们先不要管RT的进程,反正富贵圈乱,我们不去管他,我们只讨论一般的老百姓——non-RT进程——。在大同社会,不管那个阶层的人都有机会过程上美好的生活。无论是大山里面的孩子还是一线城市底层的孩子都有机会上清华北大,只要是分数够了就行,这是小平爷爷说的。不好意思扯远了。下面我们来看看在进程的大同社会里面,CPU 这种紧缺资源是如何分配的。
首先我们来看看下面的公式,CFS 的算法有两个重要的概念,一个是的CPU虚拟使用时间,另外一个以 CPU虚拟使用时间为依据生成一颗红黑树。
CFS中CPU虚拟使用时间公式:
actualruntime∗1024processweight \frac{actualruntime*1024}{processweight} processweightactualruntime∗1024
红黑树:
所谓红黑数,首先是一棵二叉树,多了什么逻辑呢?它是按照节点中的值做了文章。假设 parent_node 节点有两个子节点分别是:child_node_left 和 child_node_right 。这算个节点的值为:
parent_node节点上的值为:
Vparentnode V_{parentnode} Vparentnode
child_node_left 节点上的值为:
Vchildnodeleft V_{childnodeleft} Vchildnodeleft
child_node_right 节点上的值为:
Vchildnoderight V_{childnoderight} Vchildnoderight
这三个值满足如下:
Vparentnode<=Vchildnodeleft<Vchildnoderight V_{parentnode} <= V_{childnodeleft} < V_{childnoderight} Vparentnode<=Vchildnodeleft<Vchildnoderight
用大白话说,红黑树是一颗左子节点大于等于父节点,右子节点比左子节点大的二叉树。
Linux 的进程调度程序总是选择最左边的叶子节点来执行。我们看看进程会优先排到最左边的叶子节点。
actualruntime∗1024processweight \frac{actualruntime*1024}{processweight} processweightactualruntime∗1024
根据上面的公式可以知道:
- 实际运行时间短的进程 , vruntime 越小,也就是说,使用运行时间越小越会往红黑树的左边排。
- 进程的权重越大,也就是 nice 值越小,vruntime 越小,也就会将权重大的进程往右边放。这样就即权重和运行时间兼顾了,不会出现CPU只忙着那些 CPU 型的进程转,那些 I/O消耗型的进程也会有较大的机会得到 CPU 的时间片。
这里要讲到 nice 值了,nice 值越大,进程的权重越小,所以越 nice 越不想占用 CPU 资源,但是为了能这种进程也能获得 CPU 资源,它的 vruntime 就会远大,当这种进程运行了一段时间后,它真实的 CPU 运行时间就会变大了,这样它的虚拟运行时间就会变大,它会被往右排。
下面实际观察一下。先来看看下面的代码:
1 #include <stdio.h>
2 #include <pthread.h>
3 #include <sys/types.h>
4
5 void *thread_fun(void *param)
6 {
7 printf("thread pid:%d, tid:%lu\n", getpid(), pthread_self());
8 while (1) ;
9 return NULL;
10 }
11
12 int main(void)
13 {
14 pthread_t tid1, tid2;
15 int ret;
16
17 printf("main pid:%d, tid:%lu\n", getpid(), pthread_self());
18
19 ret = pthread_create(&tid1, NULL, thread_fun, NULL);
20 if (ret == -1) {
21 perror("cannot create new thread");
22 return 1;
23 }
24
25 ret = pthread_create(&tid2, NULL, thread_fun, NULL);
26 if (ret == -1) {
27 perror("cannot create new thread");
28 return 1;
29 }
30
31 if (pthread_join(tid1, NULL) != 0) {
32 perror("call pthread_join function fail");
33 return 1;
34 }
35
36 if (pthread_join(tid2, NULL) != 0) {
37 perror("call pthread_join function fail");
38 return 1;
39 }
40
41 return 0;
42 }
这个程序里面会产生 2 个进程。top 命令看一下:
我们可以清楚的看到这两个线程的 CPU 利用率是差不多的。
然后,我们用renice
重新设计一下 22066 这线程的 nice 值。
renice -n -5 22066
这时候再 TOP 命令看看:
改变 nice 后,我们发现 22066 的 CPU 占用率上升了,大概是 22065 的 3 倍,nice 值 5 的权重是 1024 ,nice 值 0 的权重是 3121,所以 22066 的实际运行时间大概是 22065 的三倍,才能保证这两个线程的虚拟运行时间相同。
除此之外,还有两个设置:
/proc/sys/kernel/sched_rt_period_us:# RT进程调度的单位CPU时间 1 秒
/proc/sys/kernel/sched_rt_runtime_us:# RT进程在 1 秒中实际占用的CPU时间, 0.95秒
这个设置说明实时进程在运行时并不是完全占用CPU的, 每1秒中有0.05秒的时间可以给其它进程运行.
这样既不会对实时进程的响应时间造成太大的影响, 也避免了实时进程卡住时导致整个系统无响应.
那可以做个试验了,如果将一个死循环的进程设置成 RT,那么这个死循环的 RT 会一直占用这 CPU,其他进程只能有一点点的机会使用 CPU,例如响应鼠标的进程,就不能及时的响应我们点击鼠标的动作了。
改变 RT 进程权重的命令为:
chrt: change rt process weight
chrt -p -a -f 10 1234 # 修改调度策略为 SCHED_FIFO, 并且优先级为10