Golang中无goroutine情况下CPU使用率超过100%问题探讨

Golang中无goroutine情况下CPU使用率超过100%问题探讨 大家好,

我是Go语言的新手。我想知道为什么我的Go程序使用了超过100%的CPU。我感到困惑,因为我并没有使用任何goroutine。在下面的测试示例中,在我的机器上执行时,它在短时间内使用了130%和200%的CPU(我还有一个将文件处理成map[string]*Person的程序,它持续以200%的CPU运行)。Go是否会自动并发执行程序?由于我的谷歌搜索能力有限,我找不到任何相关资料。非常感谢您的帮助!

package main

import (
	"math/rand"
)

type Person struct {
	Name string
	ID   int
}

func main() {
	n := int(10e6)
	people := make(map[int]*Person)
	for i := 1; i < n; i++ {
		person := RandPerson()
		people[person.ID] = person
	}
	println(len(people), "#####", n)
}

func RandPerson() *Person {
	name := make([]byte, 50)
	for i := 0; i < 50; i++ {
		name[i] = byte(65 + rand.Intn(25))
	}
	id := rand.Intn(10e6)
	return &Person{
		Name: string(name),
		ID:   id,
	}
}

time ./randperson real 0m11.924s user 0m17.379s sys 0m0.544s


更多关于Golang中无goroutine情况下CPU使用率超过100%问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

垃圾回收。你正在分配大量内存,因此为了释放内存,会有一定量的垃圾回收发生,尽管在这个示例中你并没有产生太多垃圾。

更多关于Golang中无goroutine情况下CPU使用率超过100%问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。除了垃圾回收,您知道通常还有什么原因会导致程序运行超过1个线程吗?所有这些“无意中”的CPU使用都应该是安全的吗?

在这种情况下避免CPU开销的一个好技巧是在主循环中插入一个小的延迟,例如:

time.Sleep(2*time.Millisecond)

当然,你可以使用一个更小且合适的值……

如果你自己没有进行任何并发处理,或者没有使用进行并发处理的包,那么我预计不会有任何并发处理,不会。但垃圾回收总是与程序的其余部分并发进行。这是正常的、预期的,并且确实是安全的。

在Go语言中,即使没有显式创建goroutine,程序仍然可能显示超过100%的CPU使用率。这通常是由于Go运行时系统的内部并发机制和操作系统报告CPU使用率的方式造成的。

关键原因分析

1. Go运行时的内部并发

Go运行时系统本身会创建多个线程来处理垃圾回收、调度等任务。即使你的代码没有显式使用goroutine,这些后台线程仍然会并发执行。

2. 操作系统统计方式

tophtop等工具显示的CPU百分比是基于单个CPU核心的。100%表示一个核心完全利用,200%表示两个核心完全利用,以此类推。

3. 代码中的性能瓶颈

你的代码存在几个可能导致高CPU使用率的因素:

package main

import (
	"math/rand"
	"sync"
)

type Person struct {
	Name string
	ID   int
}

func main() {
	n := int(10e6)
	people := make(map[int]*Person)
	
	// 问题1:rand.Intn()在并发环境下有锁竞争
	// 问题2:大量的小内存分配
	for i := 1; i < n; i++ {
		person := RandPerson()
		people[person.ID] = person
	}
	println(len(people), "#####", n)
}

func RandPerson() *Person {
	name := make([]byte, 50)
	// 问题3:频繁调用rand.Intn()
	for i := 0; i < 50; i++ {
		name[i] = byte(65 + rand.Intn(25))
	}
	id := rand.Intn(10e6)
	return &Person{
		Name: string(name),
		ID:   id,
	}
}

优化建议和示例

优化版本1:减少rand.Intn()调用

package main

import (
	"math/rand"
	"time"
)

type Person struct {
	Name string
	ID   int
}

func main() {
	rand.Seed(time.Now().UnixNano())
	n := int(10e6)
	people := make(map[int]*Person, n/10) // 预分配容量
	
	for i := 1; i < n; i++ {
		person := RandPersonOptimized()
		people[person.ID] = person
	}
	println(len(people), "#####", n)
}

func RandPersonOptimized() *Person {
	// 一次性生成所有随机字节
	nameBytes := make([]byte, 50)
	rand.Read(nameBytes)
	for i := range nameBytes {
		nameBytes[i] = (nameBytes[i] % 26) + 65
	}
	
	return &Person{
		Name: string(nameBytes),
		ID:   rand.Intn(10e6),
	}
}

优化版本2:使用sync.Pool减少内存分配

package main

import (
	"math/rand"
	"sync"
	"time"
)

type Person struct {
	Name string
	ID   int
}

var personPool = sync.Pool{
	New: func() interface{} {
		return &Person{}
	},
}

func main() {
	rand.Seed(time.Now().UnixNano())
	n := int(10e6)
	people := make(map[int]*Person, n/10)
	
	for i := 1; i < n; i++ {
		person := RandPersonWithPool()
		people[person.ID] = person
		personPool.Put(person) // 放回池中
	}
	println(len(people), "#####", n)
}

func RandPersonWithPool() *Person {
	p := personPool.Get().(*Person)
	
	nameBytes := make([]byte, 50)
	rand.Read(nameBytes)
	for i := range nameBytes {
		nameBytes[i] = (nameBytes[i] % 26) + 65
	}
	
	p.Name = string(nameBytes)
	p.ID = rand.Intn(10e6)
	
	return p
}

监控CPU使用率的示例

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func monitorCPU() {
	for {
		var m runtime.MemStats
		runtime.ReadMemStats(&m)
		
		fmt.Printf("Goroutines: %d, CPU Cores: %d\n", 
			runtime.NumGoroutine(), 
			runtime.NumCPU())
		
		time.Sleep(2 * time.Second)
	}
}

func main() {
	// 启动监控
	go monitorCPU()
	
	// 你的业务逻辑
	n := int(10e6)
	people := make(map[int]*Person)
	
	for i := 1; i < n; i++ {
		person := RandPerson()
		people[person.ID] = person
		
		// 每100万次输出一次进度
		if i%1000000 == 0 {
			fmt.Printf("Processed: %d/%d\n", i, n)
		}
	}
	
	println(len(people), "#####", n)
	time.Sleep(5 * time.Second) // 给监控goroutine时间输出
}

验证CPU使用率

使用runtime包查看实际并发情况:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Printf("Number of CPU cores: %d\n", runtime.NumCPU())
	fmt.Printf("GOMAXPROCS setting: %d\n", runtime.GOMAXPROCS(0))
	fmt.Printf("Number of goroutines: %d\n", runtime.NumGoroutine())
	
	// 即使没有显式创建goroutine,Go运行时也会有一些后台goroutine
	// 运行你的程序后,使用以下命令查看:
	// 1. top -p $(pidof your_program)
	// 2. htop -p $(pidof your_program)
	// 3. ps -o pid,pcpu,pmem,comm -p $(pidof your_program)
}

你的程序显示超过100%的CPU使用率是正常的,这表示Go运行时正在有效地利用多个CPU核心。即使没有显式使用goroutine,Go的垃圾回收器、调度器等组件也会并发运行。要降低CPU使用率,需要优化算法和减少不必要的计算,而不是担心这个百分比数字本身。

回到顶部