go语言的一些常见踩坑问题

news2024/12/25 2:28:52

开始之前,介绍一下​最近很火的开源技术,低代码。

作为一种软件开发技术逐渐进入了人们的视角里,它利用自身独特的优势占领市场一角——让使用者可以通过可视化的方式,以更少的编码,更快速地构建和交付应用软件,极大程度地降低了软件的开发、配置、部署和培训成本。

应用地址: https://www.jnpfsoft.com
开发语言:Java/.net

这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架。前后端封装了上千个常用类,方便扩展;采用微服务、前后端分离架构,集成了代码生成器,支持前后端业务代码生成,满足快速开发;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3,平台即可私有化部署,也支持 K8S 部署。

从其他语言刚转入go语言的时候比较容易出现以下方面的问题:

  • 字符串string
  • interface断言
  • 切片slices
  • map
  • 控制结构(for、switch)
  • defer channel管道
  • sync同步机制
  • select+timer
1.字符串String(Split分割)

项目可能使用情况: 当使用string的split功能分割空字符串时,再进行数据库模糊查询时候,如下:

img

踩坑分析: 当对空字符串进行Split,将会返回一个包含一个空字符串的切片数组,数组长度为1,但是查询时由于空字符串会被过滤掉了该条件,会导致查询出来的数据不正确,甚至可能会是全表扫,由于查询所有数据可能会系统崩溃掉。

如何避坑: 使用前可以排除空字符串

img

2.interface断言

在项目中也会经常使用类型断言,当使用interface()转化成相对应的类型时,如果不恰当使用断言而导致panic,踩坑代码:

img

踩坑分析: golang中对于类型的断言,一定需要加上第二个参数ok判断,否则类型不一致的话直接panic退出 如何避坑: 增加第二个参数ok来判断

img

3.切片slice
3.1 容量问题

要注意在make切片的时候的参数设置,参数设置有问题很容易导致取下标值不是自己想象中的值,如下:

img

踩坑分析: 一般来说,slice的初始化为 make([]T, length, capacity)。 如果省略了capacity,默认capacity等于length。因此上面建了一个[]int类型的切片,长度和容量为3的[0,0,0]切片,因此通过append(s,1)会使slice扩容成6,并添加元素1进去。输出结果为:[0,0,0,1]

如何避坑:1.使用make([]T, length, capacity)补全参数;2.使用make([]T, length),则使用通过索引方式赋值,例如,s[0]=1

3.2 截取[:n]

在项目中可能会使用到切片截取功能,如下简单的代码,那么会出现什么问题呢?

img

踩坑分析: 因为切片的截取是引用关系,共有 2 个切片 a 和 b,截取了 a 的一部分赋值给了 b,两者存在着关联。图3-2-1 因此,虽然切片 a 只有底层数组中 0 和 1 两个索引位正在被使用,其余未使用的底层数组空间毫无作用,图3-2-2。但由于正在被引用,他们也不会被 GC,因此造成了内存泄露。

img

图3-2-1

img

图3-2-2

如何避坑: 可以通过拷贝的方式,同时将原有的切片或者数组释放。

img

4.map
4.1nil的map赋值问题

在项目中也经常使用到map,但是对于map的使用也很容易出错,比如,对一个nil的map进行赋值:

img

踩坑分析: 对未初始化的map变量,添加元素时会空指针panic,抛出错误:

img

如何避坑:往map添加元素时需要先分配内存。 例如 m := make(map[int]int)

4.2 判断map中的key是否存在

在使用map的key取值时,需要先判断key是否存在,踩坑代码:

img

如何避坑: 不能通过取出来的值来判断key是否存在map中。需要采用如下的形式:

  if _, ok := m[1]; !ok {
            print("key not exists")
        }
​
4.3map的遍历顺序问题

在使用map for循环时,也会出现一些踩坑问题,比如,判断map两次循环相同顺序的值是否一致。

img

踩坑分析: map的遍历时,golang会提前取一个随机数,把桶的遍历顺序随机化。因此,在程序中,不能依赖遍历的顺序。 如何避坑: 如果需要确保遍历顺序,一般需要自行维护一个额外的有序的数据结构。比如,使用list+sorts

img

4.4 map的并发读写

在使用map时需要注意,map写入和读取操作是否存在并发问题,特别是引用第三方库的时候,比较容易出现并发map操作的问题,比如:

img

踩坑分析: golang中的普通map不是线程安全的,如果并发读写,会导致panic。出现这样的错误:

img

如何避坑:不要并发读写map,也即不要在多个goroutine中同时对map进行读和写。如果一定要有读和写,可以使用sync.Map,但是sync.Map性能比较低,小心使用。

5.控制结构
5.1for循环取址问题

在项目中经常使用for循环进行遍历,但是很容易在指针类型上使用错误,比如:

img

踩坑分析: 因为在循环里创建的所有函数变量共享相同的变量,其实就是一个可访问的存储位置,而不是固定的值。 因此在for多次循环中,value的地址只有一个。比如,在上面的循环变量p中,在每次迭代中只给它分配了一个新值,而循环变量的地址在每次迭代中都是相同的,因此将存储相同的指针。因此,上面的遍历中,在循环之后,它将保存在最后一次迭代中分配的值。因此运行以上代码,输出如下,和预期不一样:

img

如何避坑

(1).在上面的case中不要使用指针

(2).在本地赋予一个临时指针,使用临时指针进行赋值,就不会被覆盖。

 for _, p := range persons {
               innerP := p
               personMap[p.name] = &innerP
       }
5.2 for必包问题

在项目中也经常使用for循环进行启动协程,在使用协程的时候,需要注意的for循环体中的变量也是一样,比如:

img

踩坑分析: 这个问题和上面的指针问题类似,因为for遍历非常快,所以当for遍历完毕后,v的值是最后的值。因此,在go闭包函数运行的时候,打印的全部都是最新的值。 如何避坑

在循环中的闭包,应该使用传参的方式,将变量传入函数中。这个时候会发生一次拷贝,因此,不会被其它的变量所覆盖:

for _, v := range s {
  go func(v string) {
     println(v)
  }(v)
}
​

或者使用临时变量,将循环体中值重新赋值给临时变量中:

for _, v := range s {
    tempV:=v
  go func() {
     println(tempV)
  }()
} 
5.3 switch多个case问题

在项目中也会使用到switch,但是由于go语言跟其他的语言的switch,也很容易误以为多个case放在一起能够接着执行,如下:

package main
 
import "fmt"
 
func main() {
    i := 1
    switch i {
    case 1:
    case 2:
        fmt.Println("ok")
    }
    fmt.Println("end")
}
​

踩坑分析: golang的switch和其它语言差别很大。像Java/c等,上面的情况可能使case 1和case 2都执行到了下面的语句。但是golang会自动为每个case增加break。 因此,上面执行到了case 1之后就退出了。 如何避坑:如果需要上面的case满足预期,可以在case1后面增加fallthrougth语句。 或者直接case1, 2多个条件一起。

package main
 
import "fmt"
 
func main() {
    i := 1
    switch i {
    case 1:
     fallthrougth
    case 2:
        fmt.Println("ok")
     fallthrougth
    }
    fmt.Println("end")
}
​
6.defer问题
6.1 defer在跨协程的问题

在项目中defer经常在使用func方法最前面,进行捕获一些非法异常,但是也很容易忽略了跨协程的问题,比如:

//PublishBusiness 发布 
func PublishBusiness(ctx context.Context, businessId int64) error {
    var e error
      defer func() {
        if e !=nil{
      logger.CtxLogErrorf(ctx, "PublishBusiness err: %v", err)
​
        }
     }()
     //更新
     e = b.doPublishBusiness(c, businessId)
     go func() {
        1/0 //子协程 pianc
      }() 
  return err
}
​

踩坑分析: defer 只会在当前函数返回前执行传入的函数,理解这句话主要在三个方面:当前函数返回前执行传入的函数,即 defer 关键值后面跟的是一个函数,包括普通函数如(fmt.Println), 也可以是匿名函数 func() 因此,在使用recover时,必须在同一个goroutine中使用才可以捕获panic。上面出现panic是在子goroutine中,因此无法捕获,会导致程序crash中断退出。 如何避坑:一般启动一个goroutine时,必须在该goroutine中处理panic,使用defer捕获一下。

6.2 循环中使用defer

在项目中会使用到for循环打开文件,但是在关闭文件的时候容易出现问题,比如:

package main
 
import (
    "log"
    "os"
)
 
func main() {
    for i := 0; i < 10; i++ {
        f, err := os.Open("/path/file")
        if err != nil {
            log.Fatalln(err)
        }
        defer f.Close()
    }
}
​

踩坑分析: 因为defer是在整个函数运行完毕之后才会执行。因此上面的代码中,会出现内存泄漏问题,因为在循环中,每个defer函数会压入到堆栈中。等到整个main函数执行完毕,才从堆栈中弹出来defer函数进行执行。假如循环比较大,而且里面的执行比较重,那么会严重影响性能。

如何避坑:不要再for循环中使用defer函数。可以通过匿名函数将函数快速结束,从而快速执行defer函数释放资源。例如:

package main
import (
    "log"
    "os"
)
func main() {
    for i := 0; i < 10; i++ {
        func() {
            f, err := os.Open("/path/file")
            if err != nil {
                log.Println(err)
                return
            }
            defer f.Close()
        }()
    }
}
​
7.channel管道问题
7.1 channel管道panic的问题

项目经常使用协程并发,结果收集会集中在channel管道中,但在channe使用也比较容易出问题,比如:

import "time"
 
func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 1000; i++ {
            ch <- i
            time.Sleep(1)
        }
    }()
 
    go func() {
        close(ch)
    }()
    time.Sleep(100000)
}

踩坑分析: 在channel错误操作比较容易影响panic,下面几类:a).向已关闭的channel发送数据导致panicb).重复关闭channel会导致panicc).关闭nil channel会导致panic因此在上面的例子就是向已关闭的channel发送数据导致panic,会导致程序不可用 如何避坑: channel关闭要适当,也不要向关闭的channel中进行操作,包括发送信息,再次关闭等

7.2 channel管道死锁的问题

因为在channel存在生产者和消费者,也容易出现问题,比如:

 package main
 
   func main() {
   ch := make(chan int)
   ch <- 1
   <-ch
   }
​

踩坑分析: 造成死锁的原因:循环等待、资源共享、非抢占式, 在并发中出现通道死锁有两种情况:数据要发送,但是没有人接收数据要接收,但是没有人发送 因此上面就是,因为生产者和消费者在同一个goroutine中,因此无法并行执行,导致发送的消息一直无法被消费掉,而在ch<- 1一直阻塞着,出现死锁。 如何避坑: 生产者和消费者不能属于同一个goroutine,且生成者和消费者应该成对出现

8.sync同步机制panic问题

在并发下,sync同步机制也经常使用,也是比较容易出现问题的,比如,sync.Mutex:

package main
 
import "sync"
 
func main() {
    var r sync.Mutex
    r.Lock()
    r.Unlock()
    r.Unlock()
}
​

踩坑分析: 在同步机制上造成panic会有以下情况: a).sync.Mutex 没有加锁就进行解锁而导致panic b).sync.Mutex 重复解锁而导致panic c).sync.WaitGroup 计数为负而导致panic 因此上面就是,sync.Mutex 重复解锁而导致panic 如何避坑: 加锁和解锁配对出现

select+timer

项目中在一些情况下需要进行超时控制,使用select+timer去解决超时控制,这边也会有一个坑,比如:

package main
import (
    "fmt"
    "time"
)
 
func main() {
    ch := make(chan string)
    go func() {
        for i := 0; i < 100; i++ {
            ch <- "ok"
        }
    }()
 
    for {
        select {
        case v := <-ch:
            fmt.Println(v)
        case <-time.After(time.Second * 10):
            fmt.Println("timeout")
        }
    }
}
​

踩坑分析: 在for循环每次select的时候,都会实例化一个一个新的定时器。该定时器在10秒后 ,才会被激活,但是激活后已经跟select无引用关系,被gc给清理掉。 换句话说,被遗弃的time.After定时任务还是在时间堆里面,定时任务未到期之前,是不会被gc清理的。因此,会出现内存泄漏的现象。

如何避坑

a).改为timer的方式:

ticker := time.NewTicker(3 * time.Second)
for {
  <-ticker.C
  fmt.Println("timeout")
}
​

b).使用context.WithTimeout方式:

ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
     defer cancel() 
     select {
  case <-ch:return true
  case <-ctx.Done():return false
}
​

go​编辑程序员

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1684734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker 安装minio 服务 ssl 证书

minio 安装 ssl 证书 下载apache 证书 &#xff08;可以使用免费的证书&#xff09; 放在/opt/minio/conf/certs 下 (安装minio 时的 挂载目录 参考文章 docker 安装minio,详细图解 ) 拷贝进容器 /root/.minio docker cp /opt/minio/conf/certs/private.key minio:/root/.mi…

蜂窝物联四情监测:助力农业升级,科技赋能打造丰收新篇章!

农业四情指的是田间的虫情、作物的苗情、气候的灾情和土壤墒情。“四情”监测预警系统的组成包括管式土壤墒情监测站、虫情测报灯、气象站、农情监测摄像机&#xff0c;可实时监测基地状况,可以提高监测的效率和准确性&#xff0c;为农业生产提供及时、科学的数据支持&#xff…

知识图谱数据预处理笔记

知识图谱数据预处理笔记 0. 引言1. 笔记1-1. \的转义1-2. 特殊符号的清理1-3. 检查结尾是否正常1-4. 检查<>是否存在1-5. 两端空格的清理1-6. 检查object内容长时是否以<开始 0. 引言 最近学习知识图谱&#xff0c;发现数据有很多问题&#xff0c;这篇笔记记录遇到的…

【android 9】【input】【2.结构体含义】

系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录…

怎么认识和应用Redis内部数据结构?no.22

Redis 内部数据结构 RdeisDb Redis 中所有数据都保存在 DB 中&#xff0c;一个 Redis 默认最多支持 16 个 DB。Redis 中的每个 DB 都对应一个 redisDb 结构&#xff0c;即每个 Redis 实例&#xff0c;默认有 16 个 redisDb。用户访问时&#xff0c;默认使用的是 0 号 DB&#…

NLP(18)--大模型发展(2)

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 LLM的结构变化&#xff1a; Muti-head 共享&#xff1a; Q继续切割为muti-head,但是K,V少切&#xff0c;比如切为2个&#xff0c;然后复制到n个muti-head减少参数量&#xff0c;加速训练 attention结构改动&#xff1a; s…

数据安全保护的权益有什么?

针对个人主体&#xff0c;数据需要保护的权益有&#xff1a; 个人的隐私安全、社交安全、财产安全、支付安全、各类权利安全、生命安全、声誉安全 这些权益在物理世界中也基本都是存在的&#xff0c;只不过在数字世界中进行了映射或者重构。 针对企业的主体&#xff0c;需要保…

非常简单的副业兼职,知乎问答。每天半小时,收益240+

近期平台陆续为我带来了约240元的收入。实现这一目标并不需要复杂的方法或技巧&#xff0c;甚至可以说是零粉丝、零门槛。只需每天抽出半小时进行复制粘贴操作即可。在此&#xff0c;我希望能为那些缺乏基础的小伙伴们提供一些微不足道的经验和启示。 周周近财&#xff1a;让网…

(六)DockerCompose安装与配置

DockerCompose简介 Compose 项目是 Docker 官方的开源项目&#xff0c;负责实现对 Docker 容器集群的快速编排。使用前面介绍的Dockerfile我们很容易定义一个单独的应用容器。然而在日常开发工作中&#xff0c;经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现…

HQL面试题练习 —— 互相关注

目录 1 题目2 建表语句3 题解 1 题目 现有用户关注者列表记录表 t_user_follower&#xff0c;有两个字段&#xff0c;用户ID&#xff08;user_id&#xff09;&#xff0c;关注者列表&#xff08;follower_ids)&#xff0c;关注者列表中是关注用户的用户ID&#xff0c;数据样例如…

JavaEE-文件IO1

文章目录 一、什么是文件IO?1.1 IO1.2 文件1.2.1 路径1.2.2 文件分类 二、使用Java针对文件系统进行操作 一、什么是文件IO? 1.1 IO IO分别代表Input和Output即输入和输出。比如我的电脑可以从网络上下载文件&#xff0c;也可以通过网络上传文件或者我把我的内存中的数据保…

第 8 章 机器人实体导航实现01_准备工作(自学二刷笔记)

重要参考&#xff1a; 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 9.3.1 导航实现01_准备工作 1.1分布式架构 分布式架构搭建完毕且能正常运行&#xff0c;在PC端可以远程登陆…

性能测试——性能问题分析步骤

前言 性能测试大致分以下几个步骤&#xff1a; 需求分析 脚本准备 测试执行 结果整理 问题分析 今天要说的是最后一个步骤——“问题分析”&#xff1b; 需求描述 有一个服务&#xff0c;启动时会加载一个1G的词表文件到内存&#xff0c;请求来了之后&#xff0c;会把…

【前端】使用 Canvas 实现贪吃蛇小游戏

使用 Canvas 实现贪吃蛇小游戏 在这篇博客中&#xff0c;我们将介绍如何使用 HTML5 Canvas 和 JavaScript 实现一个简单的贪吃蛇&#xff08;Snake&#xff09;小游戏。这个项目是一个基础的游戏开发练习&#xff0c;它可以帮助你理解如何在 Canvas 上绘图、如何处理用户输入以…

【Spring security】【pig】Note03-pig token令牌解析器过程

&#x1f338;&#x1f338; pig token令牌解析器过程 &#x1f338;&#x1f338; pig后端源码 一、解析请求中的令牌值。 二、验证令牌 内省并验证给定的令牌&#xff0c;返回其属性。返回映射表示令牌有效。 /*** author lengleng* date 2019/2/1 扩展用户信息*/ publi…

重新安装vmware与再次编译u-boot

一、使用环境&#xff1a; 使用vmware 16pro安装 ubuntu18.04桌面版 二、遇到的问题与解决&#xff1a; 1&#xff09;、无法连网&#xff1a; 保持nat模式&#xff0c;移除再添加。 2&#xff09;、git配置私钥&#xff1a; 如果是拉取自己的仓库&#xff0c;请查看此步&am…

电路笔记 :元器件焊接相关 酒精灯松香浴加热取芯片

记录一下只使用松香和小火源加热&#xff08;如酒精灯、小蜡烛&#xff09;从电路板中取芯片。 过程 多放松香 让松香淹没芯片尽量均匀加热&#xff0c;等芯片旁边的松香开始从芯片里冒细小的“泡泡”&#xff0c;就差不多了 注&#xff1a;这种方法也可以用于焊接&#xff0…

232COM串口通讯读写NFC卡C#示例源码

本示例使用的发卡器&#xff1a;RS232串口RFID NFC IC卡读写器可二次开发编程发卡器USB转COM-淘宝网 (taobao.com) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using Syste…

【技术实操】中标麒麟高级服务器操作系统实例分享,rsync数据同步配置方案

1.rsync介绍 rsync是一款开源的、快速的、多功能的、可实现全量及增量的本地或远程数据同步备份工具。 在守护进程模式&#xff08;daemon mode&#xff09;下&#xff0c;rsync默认监听TCP端口873&#xff0c;以原生rsync传输协议或者通过远程shell如RSH或者SSH提供文件。SS…

App Inventor 2 如何接入ChatGPT:国内访问OpenAI的最佳方式

如何接入OpenAI 由于国内无法访问OpenAI&#xff0c;KX上网可选大陆及香港&#xff08;被屏蔽&#xff09;以外才行。因此对于大多数人来说&#xff0c;想体验或使用ChatGPT就不太便利&#xff0c;不过App Inventor 2 为我们提供了相对便利的一种方式&#xff0c;即“试验性质…