负载是一个非常具有欺骗性的数字。撒一粒盐。
如果您快速连续地产生许多任务并且很快完成,则运行队列中的进程数太少而无法为其注册负载(内核每五秒钟对负载计数一次)。
考虑这个示例,在我的具有8个逻辑内核的主机上,此python脚本将在顶部最多注册大量CPU使用率(大约85%),但几乎没有负载。
import os, sys
while True:
for j in range(8):
parent = os.fork()
if not parent:
n = 0
for i in range(10000):
n += 1
sys.exit(0)
for j in range(8):
os.wait()
另一种实现方式是避免wait
以8组为一组(这会使测试产生偏差)。在这里,父母总是试图将子代的数量保持在活动CPU的数量上,这样它将比第一种方法更忙,而且希望更准确。
/* Compile with flags -O0 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define ITERATIONS 50000
int maxchild = 0;
volatile int numspawned = 0;
void childhandle(
int signal)
{
int stat;
/* Handle all exited children, until none are left to handle */
while (waitpid(-1, &stat, WNOHANG) > 0) {
numspawned--;
}
}
/* Stupid task for our children to do */
void do_task(
void)
{
int i,j;
for (i=0; i < ITERATIONS; i++)
j++;
exit(0);
}
int main() {
pid_t pid;
struct sigaction act;
sigset_t sigs, old;
maxchild = sysconf(_SC_NPROCESSORS_ONLN);
/* Setup child handler */
memset(&act, 0, sizeof(act));
act.sa_handler = childhandle;
if (sigaction(SIGCHLD, &act, NULL) < 0)
err(EXIT_FAILURE, "sigaction");
/* Defer the sigchild signal */
sigemptyset(&sigs);
sigaddset(&sigs, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &sigs, &old) < 0)
err(EXIT_FAILURE, "sigprocmask");
/* Create processes, where our maxchild value is not met */
while (1) {
while (numspawned < maxchild) {
pid = fork();
if (pid < 0)
err(EXIT_FAILURE, "fork");
else if (pid == 0) /* child process */
do_task();
else /* parent */
numspawned++;
}
/* Atomically unblocks signal, handler then picks it up, reblocks on finish */
if (sigsuspend(&old) < 0 && errno != EINTR)
err(EXIT_FAILURE, "sigsuspend");
}
}
出现这种现象的原因是,与创建实际任务相比,该算法花费更多的时间来创建子进程(计数为10000)。尚未创建的任务不能计入“可运行”状态,但是在生成任务时会占用CPU时间%sys。
因此,答案可能确实是在您的情况下,无论完成什么工作,都会快速连续产生大量任务(线程或进程)。