Linux nice命令详解:从CPU调度原理到实战调优
2026/9/17 22:26:58 网站建设 项目流程

作为经常在服务器上跑批量任务的人,你一定遇到过这种场景:守着一台机器做数据清洗、定时备份、日志压缩,结果线上服务的响应突然就变慢了,top 一看某个进程把 CPU 吃得干干净净,其他进程全在排队等着。第一反应多半是 kill 掉这个“捣乱”的进程,但任务还没跑完,舍不得;不 kill 又怕影响核心业务。Linux 早就给了标准答案,就是标题里这个看着不起眼的命令——nice。

这篇东西我不打算写成一页命令手册,而是想把 nice 命令从概念到实操完整拆开,讲清楚它到底改了什么、什么时候有效、什么时候白改,以及我踩过的那些坑。适合刚接触 Linux 的运维新手,也适合那些用了好几年 nice 但从没真正理解过它的人。

1. 先搞清楚:nice 到底是干什么的

1.1 从一次压测事故说起

早些年在公司做性能压测,机器上同时跑着 Nginx 和一套内部的数据处理脚本。脚本是凌晨定时任务,正常情况下 CPU 占用不高,结果那天数据量翻倍,脚本直接把整个 8 核机器吃满了。Nginx 的响应延迟从 5ms 一路涨到 200ms,监控告警响成一片。当时最让我懊恼的是:这个问题本来完全可以在脚本启动命令里加一个词就能避免——就是nice -n 10

nice命令的用途,简单说就是:在启动一个进程时,给它设置一个“谦让值”,让内核在分配 CPU 时间时少分它一点,把更多执行时间留给优先级更高的进程。这个“谦让值”就是 nice value,中文社区里一般叫 nice 值或者友好值,取值范围在 Linux 上是 -20 到 19,默认是 0。

值越小,优先级越高,进程越“霸道”;值越大,优先级越低,进程越“谦让”。所以“对系统更 nice”实际上是让进程自己往后退一步,把 CPU 让给别人。这就是为什么它叫 nice,而不是叫 fast 或者 priority。

1.2 nice 值到底在调度器里怎么工作

我刚开始接触这玩意儿的时候,以为 nice 值就是设置一个 1 到 100 的百分比,后来翻了内核资料才发现完全不是。Linux 的默认调度器是 CFS(完全公平调度器),从 2.6.23 开始用到现在。CFS 的理念不是给每个进程分一个固定时间片,而是维护一个虚拟运行时间(vruntime),哪个进程实际占用 CPU 的时间越少、vruntime 越小,调度器就越倾向于让它上 CPU。

nice值的作用,就是通过改变进程的权重来影响 vruntime 的增长速度。权重越大,虚拟时间增长越慢,进程就越容易获得 CPU;权重越小,虚拟时间增长越快,进程就越容易被调度器冷落。

内核里有一个权重表,大致对应关系是:每提高一个 nice 值,权重大约降低 1.25 倍。具体数字可以感受一下:

nice 值权重(简化理解)
-20约 88761
-10约 9548
01024
10约 110
19约 15

所以 nice 0 和 nice 10 的两个进程抢同一个 CPU 核时,CPU 分配比例大约是 1024 比 110,接近 9:1。这意味着 nice 值为 10 的进程在竞争激烈时,理论上只能拿到大约 10% 的 CPU 时间。如果你对那个“罪魁祸首”脚本启动时加了nice -n 10,它依然能跑,只是跑得慢很多,但不会把整个系统拖垮。

1.3 “优先级”这个词太容易让人误会了

很多老哥会把 nice 值和任务优先级画等号,这会有误解。nice 值影响的是CPU 调度的权重,它不保证进程一定在某个时间片内执行完,也不保证实时性。它管的是“在 CPU 资源不够分的时候,大家按什么比例排队”。

另外要注意一个继承机制:子进程会继承父进程的 nice 值。你用nice -n 10 ./script.sh启动脚本,脚本里再起的子进程、孙进程,全都继承了 nice 10 这个设定,不需要逐个设置。这也是为什么在启动脚本时加一条 nice 命令,就能影响一整个进程树,特别适合批量任务场景。

2. 核心用法:nice 命令的参数与周边工具

2.1 nice 命令的常见写法

GNU 的nice命令语法是:

nice [选项] [命令 [参数...]]

最常用的就是-n选项,指定 nice 值调整量。假设我要启动一个备份脚本,并把它的 nice 值设置为 10,可以写:

nice -n 10 /opt/scripts/backup.sh

这个写法是 POSIX 标准推荐的,明确、可读性好。实际字节数上还有个简写形式,比如nice -10 /opt/scripts/backup.sh,在一些发行版上也支持,表示调整量是 10。但我不推荐这么写,原因后面会讲。

如果你想启动一个提高优先级的进程,比如某个交互式服务,可以用负数:

sudo nice -n -5 /usr/local/bin/api-server

注意这里必须用sudo,因为普通用户没有权限把 nice 值往负数方向调,内核会直接拒绝。

还有两个冷门选项:nice --help看帮助,nice --version看版本。日常用不到,但排查环境差异时偶尔会用到。

2.2 启动之后怎么查看一个进程的 nice 值

查 nice 值有几个常用入口。最简单的是ps命令:

ps -l

在输出里找到NI那一列,就是进程当前的 nice 值。如果想精确查某个进程:

ps -o pid,ni,cmd -p 1234

这里pid是进程号,ni是 nice 值,cmd是启动命令行。在top里也能看到,默认界面上半部分有一个NI列,数字越大表示越谦让。如果你平时用的是htop,它会直接显示在 NI 列里,用 F7、F8 还可以交互式调整。

还有个细节:top里往往还能看到PR列,即进程优先级(priority)。在常见的 Linux 环境中,对于普通调度策略(SCHED_NORMAL)的进程,PR大致等于20 + NI。比如 nice 值为 0,PR 显示 20;nice 值为 10,PR 显示 30。看到 PR 高不一定是坏事,只是说明你调低了优先级,让出了 CPU。

2.3 renice:进程启动之后想改怎么办

nice只能管启动那一刻,进程已经跑起来了,你就得用renice

renice的语法在不同 Linux 发行版上有点差别,我用的是 util-linux 版本,最稳的写法是:

renice -n 10 -p 1234

第二个-n 10表示要把进程 1234 的 nice 值设置为10,注意是“设置成”而不是“增加 10”。这是跟nice最大的区别。有朋友刚开始用的时候想当然,以为renice -n 10是在原来基础上加 10,结果把进程优先级改成了 20,直接被系统限制得几乎跑不动。

renice还支持按用户名和进程组调整,例如:

sudo renice -n -5 -u www-data

www-data用户的所有进程 nice 值设为 -5。这套操作在应急调优时非常实用,不用一个一个 PID 去改。

2.4 权限边界:普通用户和 root 的差异

这是新手最常踩的坑之一。

普通用户使用nicerenice时,只能把 nice 值变大,也就是让进程更谦让,不能把它变小。你运行:

nice -n -5 ./some-task

大概率会得到一个Permission denied或者Operation not permitted。内核不让普通用户随便提升进程优先级,是怕一个用户把系统资源全占死,搞挂其他用户的服务。

root 没有这个限制,可以在整个 -20 到 19 范围内随意调整。如果某个进程已经运行,并且你确信它是被误删了优先级,可以这样救急:

sudo renice -n -5 -p 1234

在容器环境里还有一层约束:即使容器内是 root,如果缺少CAP_SYS_NICE权限,设置负数的操作也可能会失败。这时候需要从宿主机入手,或者检查容器的 capabilities 配置。

3. 实操记录:在单核环境下验证 nice 的效果

3.1 准备一个能稳定占 CPU 的实验任务

理论讲再多,不如亲手跑一次。我这次实验的环境是 16 核的云主机,系统是 Ubuntu 22.04。为了直观看到效果,我决定用taskset把任务限制在同一个 CPU 核上,模拟两台进程抢一个核的场景。

先用一个 C 程序制造固定的 CPU 计算负载,循环次数自己调,保证每跑一次需要几秒钟:

#include <stdio.h> int main(void) { volatile unsigned long long x = 0; for (unsigned long long i = 0; i < 300000000ULL; i++) { x += i; } printf("%llu\n", x); return 0; }

编译:

gcc -O2 -o /tmp/burn /tmp/burn.c

基本思路是:同时启动两个完全相同的/tmp/burn进程,绑定到同一个 CPU 核心,一个用nice -n 0,另一个用nice -n 10,然后看谁先跑完。

之所以绑定同一个核,是因为如果机器上有多余空闲核,两个任务各跑各的,谁也碍不着谁,nice 的差异根本体现不出来。很多朋友说“我试了 nice 怎么没效果”,大概率就是栽在这上面。

3.2 用 taskset 锁定单核,对比 nice 0 和 nice 10

启动命令可以这样写,把每个进程的 time 输出重定向到不同文件,方便对比:

( time taskset -c 0 nice -n 0 /tmp/burn ) 2> /tmp/time0 & ( time taskset -c 0 nice -n 10 /tmp/burn ) 2> /tmp/time10 & wait cat /tmp/time0 /tmp/time10

因为两个进程几乎同时启动,同一个 CPU 核的算力需要按权重分配。理论上 nice 0 的进程能占到大约 90% 的 CPU,nice 10 的进程只有 10% 左右。

我在实际环境中得到的结果是:

  • nice 0 的进程:real 约 3.2 秒
  • nice 10 的进程:real 约 9.8 秒

虽然没有严格 9 倍那么夸张,但差距已经非常明显。同一个程序,只是启动命令里多了一个-n 10,执行时间就慢了三倍。这效果,比任何理论解释都直观。

如果你机器上 bash 的运算性能不同,可以修改 C 程序里的循环次数。我的经验是,先跑一次taskset -c 0 /tmp/burn看单任务时间,控制在 2 到 5 秒最优;太短不好观察,太长等得难受。

3.3 用 renice 动态修改正在运行的进程

启动时设置 nice 值只能管未来,已经运行的进程要改,就得实操一把renice

先启动一个普通任务:

taskset -c 0 /tmp/burn &

然后立刻查看它的 PID 和当前 nice 值:

ps -o pid,ni,cmd -p $(pgrep -f /tmp/burn | head -1)

输出里 NI 那列应该是 0。现在从另一个终端把它调成更“霸道”的负数优先级:

sudo renice -n -5 -p <PID>

再查一次:

ps -o pid,ni,cmd -p <PID>

NI 列变成了 -5。如果用top -d 1观察,能看到这个进程的 CPU 占用率明显上升,特别是在系统有负载的情况下。

反过来,如果想让某个失控进程安静下来,就调成正数。执行sudo renice -n 15 -p <PID>,然后观察它的 CPU 占用率。我见过不少同事直接 kill 掉出问题的任务,其实如果只是临时占 CPU,renice 后让它继续跑反而是更稳妥的选择。

3.4 实验结论:nice 值影响权重,但不等于硬性限速

通过上面这个实验,我建议你记住三个结论。

第一,nice改的是分配权重,不是设置一个“最多能用多少 CPU”的硬上限。如果机器上只有你这一个进程在跑,nice 值再高也能用满所有 CPU,只是当别人抢资源时你会排在后面。所以它不适合用来做严格的资源隔离。

第二,nice 值的影响和 CPU 核数、系统负载强相关。机器越闲,效果越不明显;机器越忙,效果越狠。用taskset绑核之后,效果才最接近理论值。

第三,对于 IO 密集型的进程,单纯调 nice 值基本没用,因为瓶颈不在 CPU 调度。比如一个进程在疯狂读数据库、写日志,它大部分时间在等待 IO,而不是占用 CPU,这时候 CPU 调度器根本无暇去管它。想限制这类进程,应该用ionice或者 cgroup 的 IO 权重,后面单独聊。

4. 常见问题与排查技巧实录

4.1 为什么我改了 nice 值,进程速度好像没变化

这个问题我至少被问过五次,每次排查到最后基本都是下面三个原因之一。

第一个原因是机器多核且负载不高。你有 16 核,只是跑了几个小任务,每个任务都有独立的核心可用,谁也不抢谁的,那 nice 值当然不痛不痒。CFS 调度器只有在 CPU 资源不足的时候,才会频繁计算权重做调整。仿佛马路上车少的时候,公交车和跑车速度都能开到限速上限,谁也不用给谁让道。

第二个原因是进程本身不是 CPU 密集型的。top里看到某个进程 CPU 占用很低,但你给它改了 nice 值,也没见变快,因为它瓶颈在磁盘 IO 或者网络等待上。判断方法很简单:top里看%CPU%WA,如果%WA很高,说明在等待 IO,这时候调 CPU 优先级自然没用。

第三个原因是你看错了列。topNI列是 nice 值,PR列是动态优先级,有人看到 PR 变了以为没改成功,其实改的是 NI 列。或者你用ps -ef看,这个输出默认根本不包含 NI 列,自然看不到变化。建议统一用ps -o pid,ni,cmd -p PID查看。

4.2 修改 nice 值报 Operation not permitted

这个错误多半是权限问题。普通用户想把 nice 值往负数调,或者把某个已经是很高的 nice 值继续往下调,内核都会拒绝。解决方法是使用sudo,或者把操作交给有权限的运维管理员。

还有一种容易忽略的情况:如果你在容器里运行,即便容器内是 root,也可能因为缺少CAP_SYS_NICE而失败。这时候可以检查容器启动参数,或者干脆从宿主机通过nsenter进入容器进行调试。平时写自动化脚本时,建议先判断当前用户权限,再决定是否使用负数,不然半夜被告警吵醒很痛苦。

4.3 在容器和 systemd 环境里,nice 失效了?

这个问题问的人越来越多,因为现在服务基本都在容器里跑。

先说 systemd。用 systemd 管理服务时,如果直接在 ExecStart 里写nice -n 10 xxx,大多数情况下是能生效的,但更规范的做法是在 unit 文件的[Service]段里加:

Nice=10

这样 systemd 会在启动服务时统一设置 nice 值,比包一层命令更干净,日志和状态管理也更清晰。注意负数同样需要权限,普通 systemd service 默认不一定有CAP_SYS_NICE

再说容器。Docker 对 CPU 资源的控制主要走的是 cgroup 体系,比如--cpu-shares--cpus--cpu-quota这些,和宿主机上的 nice 是两个维度的东西。在容器里往自己的进程加上 nice 值,对宿主机上其他容器的抢占行为,影响非常有限。换句话说,如果想限制某个容器的 CPU 使用,不要只靠renice,应该合理设置 Docker 的 CPU 配额参数。

4.4 常见问题速查表

问题可能原因解决思路
调了 nice 值,CPU 占用没变化多核空闲,或进程非 CPU 密集用 taskset 绑核,或用 top 确认瓶颈
Nice 值无法设置为负数普通用户权限不够使用 sudo,检查容器 capabilities
子进程没有继承 nice 值继承机制失效(很少见)检查是否通过监听 socket 或 systemd 间接拉起
进程在容器里 renice 报错缺少 CAP_SYS_NICE从宿主机调整或修改容器配置
改了 PR 列但 NI 列没变PR 是动态优先级,NI 才是 nice以 NI 列为准,不要只看 PR
进程速度反而更慢nice 值被调得过高改成 0 或适当变小

这个表如果你能保存在本地,下次排查会省不少事。

5. 结合实战聊聊我的一点使用建议

5.1 哪些场景值得用 nice

我的经验是,所有不要求实时响应、但很吃 CPU 的批量任务,都应该在启动时带上nice。比如凌晨跑的数据清洗脚本、数据库备份压缩、日志归档、代码仓库的离线打包、测试集群里的压测客户端。

具体命令大概长这样:

nice -n 10 /data/scripts/clean.sh nice -n 15 tar czf /backup/logs.tar.gz /var/log/nginx/

如果任务里面还涉及大量磁盘读写,建议和ionice搭配使用:

nice -n 10 ionice -c 2 -n 7 /data/scripts/clean.sh

ionice管 IO 调度优先级,nice管 CPU 调度优先级,两个一起用才是真正的“后台干活不打扰人”。这也是很多运维老手会忽略的组合拳。

对于已经运行但抢占资源严重的进程,不一定要 kill,先试试:

sudo renice -n 15 -p <PID>

让进程继续把活干完,只是不再嚣张,往往比直接杀掉更符合业务预期。

5.2 哪些场景别指望 nice

实时性要求高的场景别用 nice。比如语音通话、视频推流、游戏服务器,这类进程需要的不是排队权重,而是严格的调度策略,Linux 下应该用chrt设置 SCHED_FIFO 或者 SCHED_RR,根本不属于 nice 的职责范围。

做严格的资源隔离也别用 nice。想限制某个容器最多用多少核、多少 CPU 时间,用 Docker 的--cpus--cpu-quota,或者 Kubernetes 的 CPU limit。nice 只影响相对顺序,不能保证一个进程的 CPU 占用被限制在多少以内。这是很多人误解最深的地方:nice不会给进程踩刹车,它只是改变排队顺序。

5.3 我个人的一个习惯

我这几年写脚本,凡是计划任务、批量脚本、压缩备份这一类不需要实时响应的活,启动命令前面一律习惯性加一个nice -n 10。不管当前机器负载怎么样,都加,反正对任务本身影响不大,但对整机其他服务而言,多了一道保险。等到真正出事了,你会庆幸当初多了这么一行。

真的,调度器这东西,不懂的时候很神秘,理解了 nice 值怎么影响权重之后,其实就一句话:让它排队,而不是插队。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询