当前位置: 首页 > java >正文

Go语法:闭包

一、引言

        闭包在 Go 语言中是一把 "双刃剑":它能便捷捕获外部变量,却也常因变量引用机制导致意外行为,尤其在循环与多协程场景中容易引发数据混乱。理解闭包的变量捕获逻辑,掌握副本创建技巧,是写出安全可靠代码的关键。

二、核心特性

闭包是能访问外部作用域变量的匿名函数,其核心特征为:

  • 变量引用而非复制:闭包捕获的是变量本身,而非定义时的值,外部变量后续修改会直接影响闭包执行结果。
  • 生命周期延伸:被捕获的变量会随闭包一起存在,即使脱离原始作用域仍可被访问。
  • 潜在风险点:在循环或多协程中,若未妥善处理,闭包可能因共享同一变量引用导致逻辑错误(如重复使用最终值)。

三、具体场景

3.1 循环闭包

package mainimport "fmt"func main() {var funcs []func()for i := 0; i < 3; i++ {funcs = append(funcs, func() {fmt.Println(i) // 所有闭包都引用同一个i})}// 执行所有函数for _, f := range funcs {f()}
}
3
3
3

原因:所有闭包都引用了同一个变量 i,当循环结束时 i 的值为 3,所以所有函数调用都输出 3。

解决方案:在每次循环中创建一个局部变量副本

for i := 0; i < 3; i++ {i := i // 创建当前i的副本funcs = append(funcs, func() {fmt.Println(i)})
}

3.2 闭包与 goroutine 结合的问题

package mainimport ("fmt""time"
)func main() {for i := 0; i < 3; i++ {go func() {fmt.Println(i)}()}time.Sleep(time.Second) // 等待goroutine执行完毕
}
3
3
3

原因:goroutine 启动时可能循环已经执行完毕,所有 goroutine 都访问到最终的 i 值。

解决方案:通过参数传递当前值

for i := 0; i < 3; i++ {go func(num int) {fmt.Println(num)}(i) // 将当前i值作为参数传递
}

3.3 闭包中的变量捕获时机

package mainimport "fmt"func main() {x := 10f := func() {fmt.Println(x) // 捕获x}x = 20f() // 输出20,而不是定义时的10
}
ps:防止闭包的办法就是创建副本

3.4 项目中发送卡片消息使用多协程 防止闭包

    for _, arg := range baseEventDto.UserArgs {arg := arg // 避免闭包问题go func() {req := &model.SendMsgReq{AppKey:  "woa-task-center",ToUsers: &arg,CtxId:   time.Now().String(),UserId:  strconv.FormatInt(baseEventDto.OperatorID, 10),BizType: model.TeamSpaceBizType,Utype:   model.UpdateUType,MsgType: model.MsgTypeTemplateCard,Content: &model.AppMsgTemplateCard{Type:    model.MsgTypeTemplateCard,Content: baseEventDto.GenMesCard(),},}if err := r.SendAppV2Message(ctx, req); err != nil {klog.WarnCtx(ctx, "[teamSpaceEventSend] Failed to send message to company %s: %v", arg.CompanyId, err)}}()}

3.5 多协程函数执行的闭包问题

import ("fmt""time"
)func main() {a := 0// 启动3个goroutine,每次循环传递当前a的值for i := 0; i < 3; i++ {a = i // 模拟a的变化// 将当前a的值作为参数传递给匿名函数go func(val int) {// 这里使用的val是参数副本,不受后续a变化影响fmt.Printf("goroutine内的a值: %d\n", val)}(a) // 关键:传递当前a的副本}// 等待所有goroutine执行完毕time.Sleep(time.Second)
}

解决:传递副本

四、总结

判断闭包:循环时,如果函数内部赋值,且函数先定义后调用容易形成闭包

闭包原因:变量在当前循环没有保存,真正执行的时候每一层使用相同的值

解决闭包:使用副本

http://www.xdnf.cn/news/17003.html

相关文章:

  • 【银行测试】银行票据项目业务+票据测试点分析(二)
  • Android 之 网络通信(HTTP/TCP/UDP/JSON)
  • LeetCode Hot 100,快速学习,不断更
  • MySQL连接算法和小表驱动大表的原理
  • Parcel 使用详解:零配置的前端打包工具
  • 力扣经典算法篇-39-有效的数独(二维数组和三维数组的应用)
  • 机器学习第三课之逻辑回归(三)LogisticRegression
  • 【Linux】linux基础开发工具(三) 版本控制器Git、调试器 - gdb/cgdb使用、一些实用的调试技巧
  • 关于逻辑回归的相关知识大全
  • 【数据分享】南京诗歌文学地理数据集(获取方式看文末)
  • Mongo索引
  • SpringBoot项目数据脱敏(自定义注解)
  • PPT自动化 python-pptx - 10 : 表格(tables)
  • kotlin kmp 跨平台环境使用sqldelight
  • 【从零开始速通C语言1】 - 汇编语言1
  • 【前端安全】聊聊 HTML 闭合优先级和浏览器解析顺序
  • 《嵌入式数据结构笔记(一):数据结构导论与链表》
  • Libevent(5)之使用教程(4)工具
  • 对接古老系统的架构实践:封装混乱,走向有序
  • 《从原理到实践:MySQL索引优化与SQL性能调优全解析》
  • Axios介绍
  • 达梦数据库备份与还原终极指南:从基础到增量策略实战
  • k8s+isulad 国产化技术栈云原生技术栈搭建4-添加worker节点
  • 使用Database Navigator插件进行连接sqlite报错invalid or incomplete database
  • 新电脑上GitHub推送失败?全面排查与解决指南
  • 力扣经典算法篇-41-旋转图像(辅助数组法,原地旋转法)
  • 基于深度学习的医学图像分析:使用变分自编码器(VAE)实现医学图像生成
  • 华为智能家居与Spring人工智能
  • PyTorch生成式人工智能(24)——使用PyTorch构建Transformer模型
  • 06.Redis 配置文件说明