
在go语言中,我们通常期望利用多核cpu来提升并发程序的性能。然而,在某些特定场景下,例如快速创建大量不执行实际计算的空闲goroutine时,将go运行时配置为使用多个cpu核心(gomaxprocs > 1)反而可能导致程序执行时间增加,甚至比单核(gomaxprocs = 1)配置更慢。
考虑以下Go程序,它创建了十万个Goroutine,每个Goroutine立即阻塞在一个通道上,等待主Goroutine关闭通道以终止。
package main
import (
"fmt"
"runtime"
"time"
)
// waitAround 函数接收一个 channel,并在此 channel 上阻塞,直到它被关闭。
func waitAround(die chan bool) {
<-die
}
func main() {
var startMemory runtime.MemStats
runtime.ReadMemStats(&startMemory) // 记录初始内存使用情况
start := time.Now()
cpus := runtime.NumCPU() // 获取系统CPU核心数
// 设置 Go 运行时可使用的最大 CPU 核心数
// 尝试将此行改为 runtime.GOMAXPROCS(1) 进行对比
runtime.GOMAXPROCS(cpus) // 通常设置为系统核心数,以利用多核
die := make(chan bool) // 创建一个用于控制 Goroutine 终止的 channel
count := 100000 // 要创建的 Goroutine 数量
// 循环创建大量 Goroutine
for i := 0; i < count; i++ {
go waitAround(die)
}
elapsed := time.Since(start) // 记录 Goroutine 创建所花费的时间
var endMemory runtime.MemStats
runtime.ReadMemStats(&endMemory) // 记录结束时内存使用情况
fmt.Printf("启动了 %d 个 Goroutine\n%d 个 CPU 核心\n耗时 %f 秒\n",
count, cpus, elapsed.Seconds())
fmt.Printf("启动前内存分配 %d 字节\n启动后内存分配 %d 字节\n", startMemory.Alloc,
endMemory.Alloc)
fmt.Printf("当前运行中的 Goroutine 数量 %d\n", runtime.NumGoroutine())
// 计算每个 Goroutine 的大致内存开销
fmt.Printf("每个 Goroutine 大约占用 %d 字节\n", (endMemory.Alloc-startMemory.Alloc)/uint64(runtime.NumGoroutine()))
close(die) // 关闭 channel,释放所有阻塞的 Goroutine
}当在多核系统上运行上述代码时,如果 runtime.GOMAXPROCS 设置为系统核心数(例如 runtime.GOMAXPROCS(cpus)),程序可能会比设置为 runtime.GOMAXPROCS(1) 时执行得更慢。例如,在某些环境下,多核配置可能耗时0.5秒,而单核配置仅耗时0.15秒。这种反直觉的性能表现,促使我们深入探究Go调度器在不同核心配置下的行为差异。
Go语言的调度器负责将Goroutine映射到操作系统线程上执行。其核心模型是M-P-G模型:
当 runtime.GOMAXPROCS(1) 被设置时,Go运行时将只使用一个P和一个M。在这种配置下,Goroutine的创建和调度开销显著降低,主要原因如下:
因此,在单核模式下,创建这些空闲Goroutine主要表现为Go运行时内部的数据结构分配和链表操作,其效率非常高。
当 runtime.GOMAXPROCS 设置为大于1的值时,Go运行时会创建多个P,并可能使用多个M来并行执行Goroutine。这引入了额外的复杂性和开销:
这种“多核反而更慢”的现象并非Go语言的普遍缺陷,而是在特定场景下,Goroutine调度器在协调并发资源时所产生的固有开销。
结论:在Go语言中,GOMAXPROCS 的默认值(通常是 runtime.NumCPU())对于大多数CPU密集型或I/O密集型应用来说是最佳选择。然而,理解调度器在极端场景下的行为,如本例所示的空闲Goroutine快速创建,有助于我们更深入地掌握Go并发模型的内部工作原理,并在必要时进行精细调优。在实践中,我们应始终基于实际工作负载进行性能测试和分析,而不是仅仅依赖于直觉。
以上就是Go Goroutine创建效率探究:多核环境下的调度开销分析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号