0

0

Go语言中sync.WaitGroup指针地址的深度解析与正确打印实践

DDD

DDD

发布时间:2025-12-05 14:11:02

|

399人浏览过

|

来源于php中文网

原创

Go语言中sync.WaitGroup指针地址的深度解析与正确打印实践

本教程深入探讨go语言中指针地址的理解,特别是在处理sync.waitgroup等返回指针的场景。文章将通过具体代码示例,详细解释变量地址与变量所指向值地址之间的关键区别,纠正常见的打印误区,并指导读者如何正确获取和输出目标内存地址,以避免混淆和确保程序行为符合预期。

Go语言中的指针基础

在Go语言中,指针是一种特殊的变量,它存储另一个变量的内存地址。理解指针的关键在于区分“变量本身的值”和“变量的内存地址”。

  • & 运算符:用于获取一个变量的内存地址,返回一个指向该变量的指针。
  • * 运算符:用于解引用指针,获取指针所指向地址上的值。

例如,如果 x 是一个 int 类型的变量,那么 &x 的类型是 *int,它存储了 x 的内存地址。

sync.WaitGroup与指针传递的场景分析

考虑以下Go语言代码片段,它展示了一个使用sync.WaitGroup进行并发同步的常见模式:

package main

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

func Run() *sync.WaitGroup {
    var wg sync.WaitGroup // 声明一个 WaitGroup 实例
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Printf("goroutine 内部 wg 实例地址: %p\n", &wg) // 打印 wg 实例的地址
        time.Sleep(5 * time.Second)
        fmt.Println("goroutine 唤醒")
    }()

    fmt.Printf("Run 函数返回前 wg 实例地址: %p\n", &wg) // 打印 wg 实例的地址
    return &wg // 返回 wg 实例的地址
}

func main() {
    runtime.GOMAXPROCS(3)
    wg := Run() // main 函数中的 wg 接收 Run 函数返回的地址,此时 wg 本身是一个指针变量
    fmt.Printf("main 函数中 wg 变量的地址: %p\n", &wg) // 打印 wg 这个指针变量本身的地址
    wg.Wait()
    fmt.Println("main 函数结束")
}

这段代码的典型输出可能如下所示:

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

Run 函数返回前 wg 实例地址: 0xc000014080
main 函数中 wg 变量的地址: 0xc00000e028
goroutine 内部 wg 实例地址: 0xc000014080
goroutine 唤醒
main 函数结束

观察输出,Run函数内部和goroutine内部打印的wg地址是相同的 (0xc000014080),这符合预期,因为它们都引用的是Run函数中创建的同一个WaitGroup实例。然而,main函数中打印的wg地址 (0xc00000e028) 却与前两者不同,这常常会引起困惑。

核心问题:指针变量的地址与它所指向的地址

造成上述地址差异的原因在于对Go语言中指针变量的理解不足。让我们详细解析:

  1. 在Run函数中

    通义万相
    通义万相

    通义万相,一个不断进化的AI艺术创作大模型

    下载
    • var wg sync.WaitGroup:这行代码在Run函数的作用域内声明并初始化了一个sync.WaitGroup类型的变量wg。这个wg是一个具体的WaitGroup实例。
    • &wg:在Run函数内部,&wg获取的是这个WaitGroup实例在内存中的实际地址。Run函数最终返回的就是这个地址。
  2. 在main函数中

    • wg := Run():Run()函数返回的是一个*sync.WaitGroup类型的值,即WaitGroup实例的内存地址。main函数中的局部变量wg接收了这个地址。此时,main函数中的wg变量本身就是一个指针变量,它的类型是*sync.WaitGroup。
    • fmt.Printf("main 函数中 wg 变量的地址: %p\n", &wg):这里是问题的关键。当你在main函数中对wg(它已经是一个指针变量)使用&运算符时,你得到的是main函数中这个局部指针变量wg本身的内存地址,而不是它所指向的WaitGroup实例的地址。
    • 为了获取main函数中wg指针变量所存储的地址(即WaitGroup实例的地址),你应该直接打印wg变量的值,因为wg变量的值就是那个地址。

简而言之,&wg(当wg本身是*sync.WaitGroup类型时)获取的是存储该指针的内存位置,而wg(当wg本身是*sync.WaitGroup类型时)获取的是该指针所指向的sync.WaitGroup实例的内存位置。

正确的打印方式与示例

要正确地在main函数中打印WaitGroup实例的地址,我们需要直接打印wg变量的值:

package main

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

func Run() *sync.WaitGroup {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Printf("goroutine 内部 wg 实例地址: %p\n", &wg)
        fmt.Println("goroutine 开始睡眠 5s")
        time.Sleep(5 * time.Second)
        fmt.Println("goroutine 唤醒")
    }()
    fmt.Printf("Run 函数返回前 wg 实例地址: %p\n", &wg)
    return &wg
}

func main() {
    runtime.GOMAXPROCS(3)
    wg := Run()
    // 修正后的打印,直接输出 wg (指针变量的值,即所指向的地址)
    fmt.Printf("main 函数中 WaitGroup 实例的地址 (正确): %p\n", wg)
    wg.Wait()
    fmt.Println("main 函数结束")
}

运行修正后的代码,你将看到如下输出:

Run 函数返回前 wg 实例地址: 0xc000014080
main 函数中 WaitGroup 实例的地址 (正确): 0xc000014080
goroutine 内部 wg 实例地址: 0xc000014080
goroutine 开始睡眠 5s
goroutine 唤醒
main 函数结束

现在,所有打印的地址都是一致的,它们都指向了Run函数中创建的同一个sync.WaitGroup实例。

内存逃逸 (Escaping Analysis)

值得注意的是,Run函数中的wg变量虽然是局部变量,但由于它的地址&wg被作为返回值返回,Go编译器会进行逃逸分析(Escaping Analysis)。在这种情况下,wg实例不会被分配在上(通常局部变量会分配在栈上),而是会“逃逸”到堆上进行分配。这样做的目的是确保wg实例的生命周期可以超出Run函数的执行范围,因为它被main函数和goroutine引用了。这正是为什么main函数和goroutine能够访问到同一个有效的WaitGroup实例地址的原因。

注意事项

  1. 区分&p和p:当变量p本身已经是一个指针类型(例如*T)时:
    • &p:表示指针变量p自身的内存地址。
    • p:表示指针变量p所存储的值,这个值就是它指向的另一个内存地址。 正确理解这一区别是避免Go语言中指针混淆的关键。
  2. 理解Go的内存管理:Go语言的垃圾回收机制和逃逸分析会自动处理大部分内存管理细节,但开发者仍需理解变量的生命周期和内存分配(栈或堆)对程序行为的影响。

总结

本文通过一个sync.WaitGroup的实际案例,深入探讨了Go语言中指针地址的理解和正确打印方法。核心要点在于区分“一个指针变量本身的地址”和“这个指针变量所指向的地址”。当一个函数返回一个指针时,接收该返回值的变量本身就成为了一个指针变量。要获取它所指向的实际数据结构的地址,应直接使用该指针变量;而如果对这个指针变量再次使用&运算符,则会得到该指针变量自身的存储地址。掌握这一概念对于编写正确的Go并发程序和调试内存相关问题至关重要。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1468

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

229

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

85

2025.10.17

printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

73

2023.06.20

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

282

2023.11.28

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

318

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

538

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

11

2026.01.20

热门下载

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

精品课程

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

共32课时 | 3.9万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

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

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