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. …...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...