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. …...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
