0

0

Go语言中多核环境下Goroutine分配性能分析与优化

碧海醫心

碧海醫心

发布时间:2025-10-07 13:02:00

|

1020人浏览过

|

来源于php中文网

原创

Go语言中多核环境下Goroutine分配性能分析与优化

在Go语言中,分配大量不执行实际计算且不主动让出CPU的goroutine时,多核环境下的性能可能反而不如单核。这主要是因为多核调度引入了更复杂的Go调度器内部协调机制以及潜在的操作系统级上下文切换开销,而单核模式下,这些“空闲”goroutine可能根本不会被调度执行,仅涉及简单的内存分配和回收,从而显得更快。

Go语言调度器概览

go语言的并发模型基于goroutine,这是一种轻量级的线程。go运行时负责将这些goroutine调度到操作系统线程上执行。go调度器采用m:n模型,即将n个goroutine调度到m个操作系统线程上。runtime.gomaxprocs变量控制了go程序可以使用的最大逻辑处理器(p)数量,每个p可以看作是一个独立的go调度器实例,它会绑定到一个操作系统线程(m)上。当gomaxprocs设置为1时,go程序仅使用一个p和一个操作系统线程来执行所有goroutine。

实验观察:多核下Goroutine分配的性能下降

为了深入理解GOMAXPROCS对goroutine分配性能的影响,我们来看一个具体的实验。以下Go代码创建了大量不执行实际计算的goroutine,它们立即阻塞在一个通道上,等待被关闭。

package main

import (
    "fmt"
    "runtime"
    "time"
)

func waitAround(die chan bool) {
    <-die // 阻塞,等待通道关闭
}

func main() {
    var startMemory runtime.MemStats
    runtime.ReadMemStats(&startMemory)

    start := time.Now()
    cpus := runtime.NumCPU() // 获取系统CPU核心数
    // runtime.GOMAXPROCS(cpus) // 默认或设置为多核
    runtime.GOMAXPROCS(1)    // 实验对比:设置为单核

    die := make(chan bool)
    count := 100000 // 创建10万个goroutine
    for i := 0; i < count; i++ {
        go waitAround(die) // 启动goroutine
    }
    elapsed := time.Since(start)

    var endMemory runtime.MemStats
    runtime.ReadMemStats(&endMemory)

    fmt.Printf("Started %d goroutines\n%d CPUs\n%f seconds\n",
        count, runtime.GOMAXPROCS(-1), elapsed.Seconds()) // GOMAXPROCS(-1) 获取当前设置
    fmt.Printf("Memory before %d\nmemory after %d\n", startMemory.Alloc,
        endMemory.Alloc)
    fmt.Printf("%d goroutines running\n", runtime.NumGoroutine())
    fmt.Printf("%d bytes per goroutine\n", (endMemory.Alloc-startMemory.Alloc)/uint64(runtime.NumGoroutine()))

    close(die) // 关闭通道,释放所有阻塞的goroutine
}

在典型的多核机器上,当runtime.GOMAXPROCS(cpus)(或不设置,Go 1.5+默认使用所有核心)运行时,程序可能需要约0.5秒。然而,如果将runtime.GOMAXPROCS(1)设置为单核模式,执行时间却可能显著缩短到约0.15秒。这种反直觉的现象表明,在某些特定场景下,多核环境可能引入额外的开销。

性能差异解析

单核模式下的高效性 (GOMAXPROCS(1))

当runtime.GOMAXPROCS(1)时,Go运行时仅使用一个逻辑处理器P和一个操作系统线程M。在这种情况下,程序的主goroutine(main函数)是唯一一个实际在运行的goroutine。由于main函数在循环中不断地创建新的waitAround goroutine,并且在这些goroutine被创建后,它并没有主动让出CPU(例如通过runtime.Gosched()或I/O操作)。

因此,在main函数执行完毕并计算时间之前,所有新创建的waitAround goroutine实际上都没有机会被Go调度器调度到M上执行。它们仅仅是作为数据结构被分配到内存中,并注册到Go运行时中。当main函数执行到close(die)时,这些goroutine才会被唤醒并最终退出。整个过程主要涉及内存分配和内部簿记,Go调度器无需进行复杂的上下文切换,因此效率极高。

立即学习go语言免费学习笔记(深入)”;

笔尖Ai写作
笔尖Ai写作

AI智能写作,1000+写作模板,轻松原创,拒绝写作焦虑!一款在线Ai写作生成器

下载

多核模式下的开销 (GOMAXMAXPROCS(N > 1))

当runtime.GOMAXPROCS设置为大于1的值时,Go运行时会启动多个逻辑处理器P,并可能绑定到多个操作系统线程M。在这种多核环境下,Go调度器会尝试将新创建的goroutine均匀地分配到这些可用的P上。

这种分配和调度尝试引入了额外的开销:

  1. 调度器复杂性增加: 多个P之间需要协调,以确保goroutine的公平调度和负载均衡。这涉及到锁、原子操作以及P之间的通信,增加了调度决策的复杂性。
  2. 潜在的操作系统级上下文切换: 与单核模式不同,在多核模式下,新创建的waitAround goroutine 有更大的机会 在main函数完成其循环之前,被Go调度器实际调度到某个P上,并开始执行(尽管它们会立即阻塞)。一旦goroutine开始在不同的操作系统线程上运行,就可能涉及操作系统层面的上下文切换,这比Go调度器内部的goroutine切换要慢几个数量级。
  3. 缓存一致性开销: 如果goroutine在不同的CPU核心上运行,可能会导致CPU缓存失效和缓存一致性协议的额外开销。

简而言之,在多核环境下,Go调度器为了实现并发性,会付出更多的努力去“准备”让goroutine运行,即使这些goroutine最终什么也没做。而单核模式下,由于主goroutine的“霸占”,这些“空闲”goroutine甚至没有获得执行的机会,从而避免了大部分调度开销。

注意事项与总结

  • 场景特殊性: 这种性能差异主要发生在创建大量“空闲”且不主动让出CPU的goroutine的极端情况下。对于执行实际计算、进行I/O操作或主动调用runtime.Gosched()的goroutine,多核环境通常能显著提升性能。
  • Go版本影响: 较新版本的Go语言调度器在抢占式调度方面有所改进,即使在单核模式下,长时间运行的goroutine也可能被抢占。但这并不会改变本例中“不执行实际工作”的goroutine的根本行为。
  • 分析工具 对于更深层次的系统行为分析,可以使用strace(在Linux上)等工具来观察程序在不同GOMAXPROCS设置下的系统调用差异,从而验证操作系统级上下文切换的发生。

总而言之,理解Go调度器在不同GOMAXPROCS设置下的行为模式至关重要。对于大多数实际应用,多核环境能够充分利用系统资源,提升并发性能。然而,在进行微基准测试或处理大量轻量级、不活跃的并发任务时,需要警惕这种由于调度器开销而可能出现的反直觉性能表现。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

536

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

22

2026.01.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

234

2023.09.06

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

446

2023.09.25

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

249

2023.10.13

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

699

2023.10.26

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

6

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.6万人学习

Git 教程
Git 教程

共21课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号