Golang主程序CPU负载在子程序关闭时上升的原因分析

Golang主程序CPU负载在子程序关闭时上升的原因分析 我有一个网络服务,在收到请求时会在后台运行一个命令:

cmd := exec.Command(instPath, settings.ExecWorkingDir+settings.ExecFileEnvPath, settings.ExecWorkingDir+settings.ExecFileLogPath, ":"+freeport, id)
	if err := cmd.Start(); err != nil {
		return nil, err
	}

	e.Pid = cmd.Process.Pid

	go func() {
		_ = cmd.Wait()
	}()

	go e.loop()
	return e, nil
func (e *Exec) loop() {
	defer e.stop()
	for e.Work {
		p, err := os.FindProcess(e.Pid)
		if err != nil {
			e.Err = true
			e.Block = true
			return
		} else {
			err := p.Signal(syscall.Signal(0))
			if err != nil {
				e.Err = true
				e.Block = true
				return
			}
		}
	}
}

循环方法检查子程序是否正在运行,如果子程序执行完成,就会退出循环并触发停止方法。这个方法工作正常。但是当子程序被终止时,网络服务(主程序)的CPU负载会上升300%。

我使用的操作系统是Linux,Elementary OS。


更多关于Golang主程序CPU负载在子程序关闭时上升的原因分析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

你的代码结构不太完整,变量e是什么类型?这个循环是个非常紧凑的循环。或许你可以在每次迭代时加入一些休眠时间:

time.Sleep(100 * time.Millisecond)

另外,为什么你要通过进程ID来查找进程并检查它是否结束,而不是直接使用cmd.Wait()方法呢?

更多关于Golang主程序CPU负载在子程序关闭时上升的原因分析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


再次强调,如果我不使用 wait() 方法,当我终止子进程时会出现“进程僵死”状态。这意味着进程变成了僵尸进程,它虽然已被终止,但主进程仍在等待子进程的某些操作。

我已经尝试为每次迭代设置延迟,但这并没有帮助。
结构体E没有特殊含义;我们假设它用于保存PID的值。我无法使用Wait()方法,因为我需要返回一些数据,而且我运行的命令可能会永久运行,这是一种守护进程,Wait()方法会等待子程序完成。

这个问题通常是由于在 loop() 函数中缺少适当的延迟或休眠机制导致的。当子进程终止后,e.Work 保持为 true,循环会以极高的频率持续检查进程状态,造成CPU空转。

以下是修复后的代码示例:

func (e *Exec) loop() {
	defer e.stop()
	for e.Work {
		p, err := os.FindProcess(e.Pid)
		if err != nil {
			e.Err = true
			e.Block = true
			return
		}
		
		err = p.Signal(syscall.Signal(0))
		if err != nil {
			e.Err = true
			e.Block = true
			return
		}
		
		// 添加休眠避免CPU空转
		time.Sleep(1 * time.Second)
	}
}

关键修改是添加了 time.Sleep(1 * time.Second),这会将检查间隔降低到每秒一次,显著减少CPU使用率。

另一种更高效的方法是使用 cmd.Wait() 的通道通知机制:

func (e *Exec) startProcess() error {
	cmd := exec.Command(instPath, settings.ExecWorkingDir+settings.ExecFileEnvPath, 
		settings.ExecWorkingDir+settings.ExecFileLogPath, ":"+freeport, id)
	
	if err := cmd.Start(); err != nil {
		return err
	}

	e.Pid = cmd.Process.Pid

	// 使用通道等待进程结束
	done := make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	go func() {
		select {
		case <-done:
			e.Err = true
			e.Block = true
			e.stop()
		}
	}()

	return nil
}

这种方法完全消除了轮询的需要,通过 cmd.Wait() 在子进程结束时自动触发回调,CPU使用率将保持正常水平。

回到顶部