Golang 避坑指南
文章目录
- 1. Channel 与 Goroutine 泄露
- 1.1 发送不接收
- 1.2 接收不发送
- 1.3 nil channel
- 2. 跳出 for-switch 或 for-select
- 3.for 迭代变量
- 3.1 闭包中的for迭代变量
- 3.2 for range 迭代变量
- 4. 循环内的 defer
- 5.defer 函数的参数值
- 6.nil interface 和 nil interface 值
- 7.结构体指针访问属性前先判空
- 8.读取有顺序需要的不能使用map结构
- 参考文献
本文将介绍 Golang 初学者容易菜的坑,希望广告 Gopher 避而远之。
1. Channel 与 Goroutine 泄露
当 channel 不恰当使用时,就可能导致 Goroutine 发生永久阻塞从而造成资源泄露。
先看一下 channel 不同状态下的读写与 close 操作的结果。
操作 | 未关闭 | 已关闭 | nil |
---|---|---|---|
发数据 | 阻塞或成功发送 | panic | 永久阻塞 |
取数据 | 阻塞或成功接收 | 成功接收或零值 | 永久阻塞 |
关闭 | 成功关闭 | panic | panic |
1.1 发送不接收
对于一个已满的 channel(buffered channel 容量已满或是 unbuffered channel),继续向 其发送数据将会导致当前goroutine阻塞。为了避免这种情况需要使用其他机制通知发送者。
// 错误示例
func produce() <-chan int {ch := make(chan int)go func() {defer close(ch)for i := 0; i < 10; i++ {ch <- i}}()return ch
}func main() {ch := produce()for num := range ch {if num == 2 {// 不想接收了,直接退出吧break}fmt.Println(num)}// 虽然此段代码能正常运行,但// produce产生goroutine将永远// 阻塞于 ch <- i上,造成资源泄露
}// 修正
func produce(doneCh chan struct{}) <-chan int {ch := make(chan int)go func() {defer close(ch)loop:for i := 0; i < 10; i++ {select {case ch <- i:case <-doneCh:break loop}}}()return ch
}
func main() {doneCh := make(chan struct{})ch := produce(doneCh)for num := range ch {if num == 2 {// 不想接收了,先通知一下生产者close(doneCh)break} fmt.Println(num)}
}
1.2 接收不发送
与前述情况相反,若接收者一直在一个不会再产生数据的 channel 上等待,将导致其所在routine 阻塞而泄露。 在Go中从一个 closed channel 读取数据:
- 不会阻塞且获取对应类型的零值
- for-range将退出
- v, ok := <-ch中ok将为false
所以可以利用上述性质通知接收方结束数据读取。
// 错误示例
func produce(doneCh chan struct{}) <-chan int {ch := make(chan int)go func() {select {case ch<-1:case <-doneCh:break}// 任务完成,直接退出}()return ch
}
func main() {doneCh := make(chan struct{})ch := produce(doneCh)for num := range ch {fmt.Println(num)}close(doneCh)// Output:// 1// fatal error: all goroutines are asleep - deadlock!
}// 修正
func produce(doneCh chan struct{}) <-chan int {ch := make(chan int)go func() {// 退出前先关闭channel防止有routine阻塞在上面defer close(ch)select {case ch<-1:case <-doneCh:break}}()return ch
}
func main() {doneCh := make(chan struct{})ch := produce(doneCh)for num := range ch {fmt.Println(num)}close(doneCh)// Output:// 1
}
1.3 nil channel
向 nil channel 发送和接收数据都将会导致阻塞。这种情况可能在我们定义 channel 时忘记初始化的时候发生。
func main() {defer func() {time.Sleep(time.Second)fmt.Println("num of routines: ", runtime.NumGoroutine())}()var ch chan intgo func() {<-ch// ch<-}()
}
2. 跳出 for-switch 或 for-select
没有指定标签的 break 只会跳出 switch/select 语句, 若不能使用 return 语句跳出的话,可为 break 跳出标签指定的代码块。
注意 goto 虽然也能跳转到指定位置,但依旧会再次进入 for-switch,死循环。
// break 配合 label 跳出指定代码块
func main() {
loop:for {switch {case true:fmt.Println("breaking out...")// break // 死循环,一直打印 breaking out...break loop}}fmt.Println("out...")
}
3.for 迭代变量
3.1 闭包中的for迭代变量
for 语句中的迭代变量在每次迭代中都会重用,即 for 中创建的闭包函数 接收到的参数始终是同一个变量,所以在 goroutine 开始执行时都会得到同一个迭代值:
// 错误示例
func main() {n := 2wg := sync.WaitGroup{}wg.Add(n)for i := 0; i < n; i++ {go func() {defer wg.Done()fmt.Print(i)}()}wg.Wait()// Output:// 22
}// 修正
func main() {n := 2wg := sync.WaitGroup{}wg.Add(n)for i := 0; i < n; i++ {num := igo func() {defer wg.Done()fmt.Print(num)}()/*当然也可以这样go func(num int) {defer wg.Done()fmt.Println(num)}(i)*/}wg.Wait()// Output:// 01 或 10
}
3.2 for range 迭代变量
for range 循环中迭代变量的短声明只会在开始时执行一次,后面都是直接赋值,所以迭代变量的变量地址是不变的,避免将其赋值给指针。
// 错误示例
slice1 := []int32{1, 2, 3, 4, 5}
slice2 := make([]*int32, len(slice1))
for i, item := range slice1 {slice2[i] = &item
}
for _, item := range slice2 {fmt.Printf("%v", *item)
}
// 55555// 修正
func Int32(v int32) *int32 {return &v
}
func main() {slice1 := []int32{1, 2, 3, 4, 5}slice2 := make([]*int32, len(slice1))for i, item := range slice1 {slice2[i] = Int32(item)}for _, item := range slice2 {fmt.Printf("%v", *item)}// 12345
}
4. 循环内的 defer
对 defer 延迟执行的函数,会在调用它的函数结束时执行,而不是在调用它的语句块结束时执行,注意区分开。
// 错误示例
type Resource struct {/*内部有一些需要释放的内容 */
}func (r Resource) Destroy() { /*...*/ }func getResource() Resource { /*...*/ }func main() {for i := 0; i < 10000; i++ {res := getResource()defer res.Destroy()// 会一直延迟至main结束才会释放// do something}
}// 修正
type Resource struct { /* 内部有一些需要释放的内容 */
}func (r Resource) Destroy() { /*...*/ }func getResource() Resource { /*...*/ }func main() {for i := 0; i < 10000; i++ {func () {res := getResource()defer res.Destroy()// 下次循环前就会释放,当然你也可以在最后直接调用Destroy// do something}()}
}
5.defer 函数的参数值
defer 只会延迟其后函数的执行,而不会延迟函数的参数的求值,若希望延迟其参数 求值,通常会加上一层匿名函数。
func main() {var i = 1times := func(num int) int {return num * 2}defer fmt.Println("resultA: ", times(i))defer func() {fmt.Println("resultB: ", func() int { return i * 2 }())}()i++// Output:// resultB: 4// resultA: 2
}
6.nil interface 和 nil interface 值
Golang 中 interface 类型变量的实现中包含值与类型,只有两者都为 nil 时该变量才为nil。
// 错误示例
type Foo interface {Bar()
}type FooImpl struct {num int
}func (f *FooImpl) Bar() { fmt.Println(f.num) }func GenFoo(num int) (Foo, error) {var f *FooImplif num != 0 {f = &FooImpl{num}}return f, nil
}func main() {f, _ := GenFoo(0)// this comparison is never trueif f == nil {return}// Panicf.Bar()
}// 正确示例
func GenFoo(num int) (Foo, error) {if num != 0 {f := &FooImpl{num}return f, nil}return nil, errors.New("num is zero")
}
那么如何判断 interface{} 的值是否为 nil 呢?
func IsNil(i interface{}) {if i != nil {if reflect.ValueOf(i).IsNil() {fmt.Println("i is nil")return}fmt.Println("i isn't nil")}fmt.Println("i is nil")
}
7.结构体指针访问属性前先判空
当结构体指针为nil时,直接访问结构体属性会报空指针
// 错误示例
type Struct1 struct {id int32
}
func main() {var a *Struct1//panic: runtime error: invalid memory address or nil pointer dereferencea.id = 1
}// 修正
type Struct1 struct {id int32
}
func main() {var a *Struct1if a != nil {a.id = 1}
}
8.读取有顺序需要的不能使用map结构
Go 里面的map存储是无序的,for循环读取与写入的顺序并不同,需要排序的功能不能使用map,而需要使用slice。
// map 读取情况
intMap := make(map[int]int, 10)for i := 0; i < 10; i++ {intMap[i] = i
}for _, v := range intMap {fmt.Println(v)
}
//9
//3
//7
//……
//没有按照写入顺序输出,乱序的// slice 读取情况
intSlice := make([]int, 0, 10)for i := 0; i < 10; i++ {intSlice = append(intSlice, i)
}for _, v := range intSlice {fmt.Println(v)
}
//0
//1
//2
//...
//读取是有序的
参考文献
Go 神坑 1 —— interface{} 与 nil 的比较 - CSDN
50 Shades of Go: Traps, Gotchas, and Common Mistakes
50 Shades of Go: Traps, Gotchas, and Common Mistakes中文翻译
如何防止 goroutine 泄露
相关文章:
Golang 避坑指南
文章目录 1. Channel 与 Goroutine 泄露1.1 发送不接收1.2 接收不发送1.3 nil channel2. 跳出 for-switch 或 for-select 3.for 迭代变量3.1 闭包中的for迭代变量3.2 for range 迭代变量 4. 循环内的 defer5.defer 函数的参数值6.nil interface 和 nil interface 值7.结构体指针…...
Java核心: JarIndex的使用
在讲解Java类加载器的时候,我们发现URLClassLoader加载类或资源时通过访问ClassPath下的每一个路径,来确定类是否存在的,假设我们执行的命令是这样的 java -classpath D:\DiveInSpring\target\classes;C:\lib\spring-expression.jar;C:\lib\…...
1052 卖个萌(测试点1,2)
solution 想要输出\需要用\\才能输出,即 cout << "Are you kidding me? \\/" << endl;测试点1,2:输入序号小于1的非法情况 #include<iostream> #include<string> #include<map> using namespace…...
Vue 3与ESLint、Prettier:构建规范化的前端开发环境
title: Vue 3与ESLint、Prettier:构建规范化的前端开发环境 date: 2024/6/11 updated: 2024/6/11 publisher: cmdragon excerpt: 这篇文章介绍了如何在Vue 3项目中配置ESLint和Prettier以统一代码风格,实现代码规范性与可读性的提升。通过设置规则、解…...
npm安装依赖过慢
今天在使用npm安装taro框架的依赖时,速度慢到吐血,使用了淘宝镜像源依然很慢,安装一个多小时没反应,最后清理了缓存再次安装速度就快很多了,因此解决方法大致有两种: 使用淘宝镜像源 原域名: ht…...
计算机毕业设计 | SpringBoot+vue的教务管理系统
1,绪论 1.1 项目背景 在这个资讯高度发展的时代,资讯管理变革已经是一个更为宽泛、更为全面的潮流。为了保证中国的可持续发展,随着信息化技术的不断进步,教务管理体系也在不断完善。与此同时,伴随着信息化的飞速发展…...
深入探索深度学习的验证集:必要还是可选?
深入探索深度学习的验证集:必要还是可选? 在深度学习项目的设计和实施过程中,数据通常被划分为训练集、测试集,以及有时的验证集。尽管在一些研究中,我们可能看到只有训练集和测试集被使用,验证集的作用及…...
初识C++ · 反向迭代器简介
目录 前言 反向迭代器的实现 前言 继模拟实现了list和vector之后,我们对迭代器的印象也是加深了许多,但是我们实现的都是正向迭代器,还没有实现反向迭代器,那么为什么迟迟不实现呢?因为难吗?实际上还好。…...
fastapi学习前置知识点
前置知识点 FastApi:一个用于构建API的现代、快速(高性能)的web框架。 FastApi是建立在Pydantic和Starlette基础上,Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包…...
机器学习常见知识点 1:Baggin集成学习技术和随机森林
文章目录 1、集成学习a.BaggingBagging的工作原理1. 自助采样(Bootstrap Sampling)2. 训练多个基学习器3. 聚合预测 Bagging的优点Bagging的缺点应用场景 b.Boosting 2、决策树3、随机森林随机森林的核心概念1. 集成学习2. 决策树 构建随机森林的步骤1. …...
容器(Docker)安装
centos安装Docker sudo yum remove docker* sudo yum install -y yum-utils#配置docker的yum地址 sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo#安装指定版本 - 可以根据实际安装版本 sudo yum install -y docke…...
前端JS必用工具【js-tool-big-box】学习,获取当前浏览器向上滚动还是向下滚动,获取当前距离顶部和底部的距离
这一小节,我们说一下 js-tool-big-box 添加的最新工具方法,在日常前端开发工作中,如果网页很长,我们就需要获取当前浏览器是在向上滚动,还是向下滚动。如果向上滚动,滚动到0的时候呢,需要做一些…...
【python】flask 框架
python flask 框架 flask是一个轻量级的python后端框架 (Django, tornado, flask) 官网:欢迎来到 Flask 的世界 — Flask中文文档(3.0.x) 安装:pip install Flask -i https://pypi.douban.com 常识: http,默认端口号为80; https,默认端口号…...
Word中插入Mathtype右编号,调整公式与编号的位置
当你已经将mathtype内置于word后,可以使用右编号快速插入公式 但是往往会出现公式和编号出现的位置或之间的距离不合适 比如我在双栏下插入公式,会发现插入的公式与编号是适用于单栏的 解决办法: 开始->样式->MTDisplayLquation -&g…...
基于【Lama Cleaner】一键秒去水印,轻松移除不想要的内容!
一、项目背景 革命性的AI图像编辑技术,让您的图片焕然一新!无论水印、logo、不想要的人物或物体,都能被神奇地移除,只留下纯净的画面。操作简单,效果出众,给你全新的视觉体验。开启图像编辑新纪元,尽在掌控! 利用去水印开源工具Lama Cleaner对照片中"杂质"进行去除…...
VMware Workstation Ubuntu server 24 (Linux) 磁盘扩容 挂载硬盘
1 Ubuntu server 关机,新增加磁盘 2 启动ubuntu虚拟机,分区和挂载磁盘 sudo fdisk /dev/sdb #查看磁盘UUID sudo blkid #创建挂载目录 sudo mkdir /mnt/data # sudo vi /etc/fstab /dev/disk/by-uuid/0b440ed0-b28b-4756-beeb-10c585e3d101 /mnt/data ext4 defaults 0 1 #加…...
表的设计与查询
目录 一、表的设计 1.第一范式(一对一) 定义: 示例: 2.第二范式(一对多) 定义: 要求: 示例: 3.第三范式(多对多) 定义: 要求…...
【react】如何合理使用useEffect
useEffect 是 React Hooks API 的一部分,它允许你在函数组件中执行副作用操作,比如数据获取、订阅或者手动更改 DOM。合理使用 useEffect 可以帮助你管理组件的生命周期行为,同时避免不必要的渲染和性能问题。以下是一些关于如何合理使用 useEffect 的建议: 明确依赖项: 当…...
计算机专业英语Computer English
计算机专业英语 Computer English 高等学校计算机英语教材 Contents 目录 Part One Computer hardware and software 计算机硬件和软件----------盖金曙 生家峰 Unit 1 the History of Computers计算机的历史 Unit 2 Computer System计算机系统 Unit 3 Di…...
目前比较好用的LabVIEW架构及其选择
LabVIEW提供了多种架构供开发者选择,以满足不同类型项目的需求。选择合适的架构不仅可以提高开发效率,还能确保项目的稳定性和可维护性。本文将介绍几种常用的LabVIEW架构,并根据不同项目需求和个人习惯提供选择建议。 常用LabVIEW架构 1. …...
CSS之块浮动
在盒子模型的基础上就可以对网页进行设计 不知道盒子模型的可以看前面关于盒子模型的内容 而普通的网页设计具有一定的原始规律,这个原始规律就是文档流 文档流 标签在网页二维平面内默认的一种排序方式,块级标签不管怎么设置都会占一行,而同一行不能放置两个块级标签 行级…...
探索GPT-4V在学术领域的应用——无需编程即可阅读和理解科学论文
1. 概述 论文地址:https://arxiv.org/pdf/2312.05468.pdf 随着人工智能潜力的不断扩大,人工智能(AI)在化学领域的应用也在迅速发展。特别是大规模语言模型的出现,极大地扩展了人工智能在化学研究中的作用。由于这些模…...
耐用充电宝有哪些?优质充电宝到底选哪个?良心推荐!
在电量即生产力的现今时代,如何为移动设备寻找一位最佳的伴侣呢?一款耐用、优质的充电宝无疑是你的不二之选。今天我们将带您揭开市场隐藏的一面,揭示哪些充电宝品牌真正代表了耐用与品质的标杆。让我们一起深入了解并选购最适合自己的充电宝…...
何为屎山代码?
在编程界,有一种代码被称为"屎山代码"。这并非指某种编程语言或方法,而是对那些庞大而复杂的项目的一种形象称呼。屎山代码,也被称为"祖传代码",是历史遗留问题,是前人留给我们的"宝藏"…...
基于esp8266_点灯blinker_智能家居
文章目录 一 实现思路1 项目简介2 项目构成3 代码实现4 外壳部分 二 效果展示UI图片 一 实现思路 摘要:esp8266,mixly,点灯blinker,物联网,智能家居,3donecut 1 项目简介 1 项目效果 通过手机blinker app…...
Web前端开发交流群:深度探索、实践与创新的集结地
Web前端开发交流群:深度探索、实践与创新的集结地 在数字时代的浪潮中,Web前端开发扮演着举足轻重的角色。为了促进前端技术的交流与发展,Web前端开发交流群应运而生,成为众多开发者学习、分享、创新的集结地。本文将从四个方面、…...
苹果AI一夜颠覆所有,Siri史诗级进化,内挂GPT-4o
苹果AI一夜颠覆所有,Siri史诗级进化,内挂GPT-4o 刚刚,苹果AI,正式交卷! 今天,苹果构建了一个全新AI帝国——个人化智能系统Apple Intelligence诞生,智能助手Siri迎来诞生13年以来的史诗级进化…...
量子计算的奥秘与魅力:开启未来科技的钥匙(详解)
目录 一、量子计算的基本概念 二、量子计算的基本原理 1.量子叠加态与相位态 一、概念 二、量子叠加态 定义与原理 特性与影响 应用领域 三、量子相位态 定义与原理 特性与影响 应用领域 2.量子门操作 一、概念 二、量子门操作的基本概念 三、常见的量子门操作…...
redis 主从同步时,是同步主节点的缓存积压区的数据,还是同步主节点的aof文件
Redis 的主从同步(replication)是同步主节点的数据到从节点上,但它既不是直接同步 AOF 文件,也不是同步缓存积压区。 当一个 Redis 从节点启动并连接到主节点时,会发生以下步骤: 同步数据集:从…...
Unity年中大促618活动又来了3折模板特效角色动画插件工具FPS生存建造模板RPG和2D素材优惠码UNITY6182024限时20240611
独立游戏开发需要找各种美术资源和模板,可以在低价时看看,节省开发时间。 Unity年中大促618活动又来了3折模板特效角色动画插件工具FPS生存建造模板RPG和2D素材优惠码UNITY6182024限时202406111104 300 款Unity引擎适配资源 3 折特惠,结账时输…...
有什么网站可以做扣扣头像/电商是做什么的
setschoolid41;select l.StartTime,l.EndTime,c.EntranceYear as 入学级,cg.Grade as 年级,c.ClassName as 班级名称,s.CName as 学生,st.StuNumber as 学号,case l.LeaveType when 1 then 事假 when 2 then 病假 end as 请假类型,l.Reason as 请假理由,case l.ApproveStatus w…...
成都市最新疫情最新消息/优化软件下载
/**abstruct 抽象类 中可以定义方法体,也可以不定义方法体。未定义方法体的需要用abstruct定义为抽象方法,则子类必须去实现抽象类的抽象方法。不可被实例化,没有构造函数。interface 接口 中不可以定义方法体,子类实现接口时&…...
这几年做哪些网站致富/营销策划方案包括哪些内容
虽然现在jdk已经已经到了14【截至2020-08-15】,但是jdk8仍然是使用最广的版本,它的新特性不仅在我们日常开发中用到,在面试中也是一个经常被提问的点,所以对一些常用的还是需要知道的,我也只写一些常用的,有…...
郑州做网站推广的公司哪家好/无排名优化
最近在网上找了个vue搭建的后台管理的框架,在使用的时候发现没有了config和build文件夹,所以当时就蒙圈了,以为是作者自己改了什么东西,所以感觉自己不知道从何下手了,不过通过查资料发现原来是vue-cli2和3的config不相…...
做电影网站要多少钱/文明seo
最近项目中需要记录服务端访问日志,所谓的服务端访问日志其实就是Controller层的访问日志,所以在开发过程中回顾了一下AOP相关的内容,特此记录,便于日后查阅。 本文只介绍Controller层日志记录,不涉及Service层与Mappe…...
凡客诚品是什么/seo网站优化平台
试验网站#1搜索引擎优化收录情况记录日期Yahoogooglebaidusogou每日收录每日收录增量每日收录每日收录增量每日收录每日收录增量每日收录每日收录增量2007-6-24288 333 1060 4813 2007-6-25164013523330108020481302007-6-26空间超过本月流量限制……,快到月底了…...