前言

在循环中,有几种情况可能会导致混乱,需要弄清楚。

循环迭代器变量中使用引用

出于效率考虑,我们经常使用单个变量来循环迭代器。但在循环中,每次循环迭代中都会有不同的值,有时候会导致未知的行为。

in := []int{1, 2, 3}

var out []*int
for _, v := range in {
out = append(out, &v)
} fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])

输出

Values: 3 3 3
Addresses: 0xc000014188 0xc000014188 0xc000014188

在out这个slice中的元素都是3。

原因:在每次迭代中,v是单个变量(内存地址不变),所以每次迭代都采用新值将 v append 到 out切片中。

最简单的解决方法是将循环迭代器变量赋值到新变量中:

in := []int{1, 2, 3}

var out []*int
for _, v := range in {
v := v
out = append(out, &v)
} fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])

新的输出:

Values: 1 2 3
Addresses: 0xc0000b6010 0xc0000b6018 0xc0000b6020

在 goroutine 中使用循环迭代变量也会有相同的问题。

list := []int{1, 2, 3}

for _, v := range list {
go func() {
fmt.Printf("%d ", v)
}()
}

输出将是:

3 3 3

请注意,如果不使用 goroutine 运行该函数,则代码将按预期运行。

循环内使用 defer

defer 的执行时机是在函数返回前,所以一般不应该在循环内部使用defer

看一段代码:

var mutex sync.Mutex
type Person struct{
Age int
} persons := make([]Person, 10)
for _, p := range persons {
func(){
mutex.Lock()
defer mutex.Unlock()
p.Age = 13
}()
}

在上面的示例中,如果使用 defer,则下一次迭代将无法获得互斥锁,因为该锁并没有释放,所以循环会永远阻塞。

通过委托给另外一个函数的方式,可以使defer提前执行。

var mutex sync.Mutex
type Person struct {
Age int
} persons := make([]Persons, 10)
for _, p := range persons {
func(){
mutex.Lock()
defer mutex.Unlock()
p.Age = 13
}()
}

这种方式特别适合多线程处理数据,导致内存使用率过大,defer用于提前释放内存

优先考虑使用接口

接口可以使代码更灵活。这是在代码中引入多态的一种方法。接口允许你定义一组行为而不是特定类型。不使用接口可能不会导致任何错误,但是会导致代码简单性,灵活性和扩展性降低。

在 Go 接口中,io.Readerio.Writer 可能是使用最多的。

type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}

这些接口非常强大,假设你要将对象写入文件,你可以定义了一个 Save 方法:

func (o *obj) Save(file os.File) error

如果第二天,你想写入 http.ResponseWriter,显然不太适合再创建另外一个 Save 方法,这时应该用 io.Writer

func (o *obj) Save(w io.Writer) error

另外,你应该知道的重要注意事项是,始终关注行为。在上面的示例中,虽然 io.ReadWriteCloser 也可以使用,但你只需要 Write 方法。接口越大,抽象性越弱。在 Go 中,通常提倡小接口。

所以,我们应该优先考虑使用接口,而不是具体类型。

结构体字段顺序

这个问题不会导致程序错误,但是可能会占用更多内存。

看一个例子:

type BadOrderedPerson struct {
Veteran bool // 1 byte
Name string // 16 byte
Age int32 // 4 byte
} type OrderedPerson struct {
Name string
Age int32
Veteran bool
}

看起来这两个类型都占用的空间都是 21字节,但是结果却不是这样。我们使用 GOARCH=amd64 编译代码,发现 BadOrderedPerson 类型占用 32 个字节,而 OrderedPerson 类型只占用 24 个字节。为什么?原因是数据结构对齐。在 64 位体系结构中,内存分配连续的 8 字节数据。需要添加的填充可以通过以下方式计算:

padding = (align - (offset mod align)) mod align
aligned = offset + padding
= offset + ((align - (offset mod align)) mod align)
type BadOrderedPerson struct {
Veteran bool // 1 byte
_ [7]byte // 7 byte: padding for alignment
Name string // 16 byte
Age int32 // 4 byte
_ struct{} // to prevent unkeyed literals
// zero sized values, like struct{} and [0]byte occurring at
// the end of a structure are assumed to have a size of one byte.
// so padding also will be addedd here as well. } type OrderedPerson struct {
Name string
Age int32
Veteran bool
_ struct{}
}

当你使用大型常用类型时,可能会导致性能问题。但是不用担心,你不必手动处理所有结构。这工具可以轻松的解决此类问题:https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment

race 探测器

数据争用会导致莫名的故障,通常是在代码已部署到线上很久之后才出现。因此,它们是并发系统中最常见且最难调试的错误类型。

Go 1.1 引入了内置的数据争用检测器(race detector)。可以简单地添加 -race flag 来使用。

$ go test -race pkg    # to test the package
$ go run -race pkg.go # to run the source file
$ go build -race # to build the package
$ go install -race pkg # to install the package

启用数据争用检测器后,编译器将记录在代码中何时以及如何访问内存,而 runtime 监控对共享变量的非同步访问。

找到数据竞争后,竞争检测器将打印一份报告,其中包含用于冲突访问的堆栈跟踪。这是一个例子:

WARNING: DATA RACE
Read by goroutine 185:
net.(*pollServer).AddFD()
src/net/fd_unix.go:89 +0x398
net.(*pollServer).WaitWrite()
src/net/fd_unix.go:247 +0x45
net.(*netFD).Write()
src/net/fd_unix.go:540 +0x4d4
net.(*conn).Write()
src/net/net.go:129 +0x101
net.func·060()
src/net/timeout_test.go:603 +0xaf Previous write by goroutine 184:
net.setWriteDeadline()
src/net/sockopt_posix.go:135 +0xdf
net.setDeadline()
src/net/sockopt_posix.go:144 +0x9c
net.(*conn).SetDeadline()
src/net/net.go:161 +0xe3
net.func·061()
src/net/timeout_test.go:616 +0x3ed Goroutine 185 (running) created at:
net.func·061()
src/net/timeout_test.go:609 +0x288 Goroutine 184 (running) created at:
net.TestProlongTimeout()
src/net/timeout_test.go:618 +0x298
testing.tRunner()
src/testing/testing.go:301 +0xe8

golang中容易遇到的错误的更多相关文章

  1. golang中的错误处理

    0.1.索引 https://waterflow.link/articles/1666716727236 1.panic 当我们执行panic的时候会结束下面的流程: package main imp ...

  2. Golang 中的坑 一

    Golang 中的坑 短变量声明  Short variable declarations 考虑如下代码: package main import ( "errors" " ...

  3. golang中Context的使用场景

    golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...

  4. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

  5. golang中发送http请求的几种常见情况

    整理一下golang中各种http的发送方式 方式一 使用http.Newrequest 先生成http.client -> 再生成 http.request -> 之后提交请求:clie ...

  6. 【荐】详解 golang 中的 interface 和 nil

    golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...

  7. [转]Golang 中使用 JSON 的小技巧

    taowen是json-iterator的作者. 序列化和反序列化需要处理JSON和struct的关系,其中会用到一些技巧. 原文 Golang 中使用 JSON 的小技巧是他的经验之谈,介绍了一些s ...

  8. Golang 中的指针 - Pointer

    http://www.cnblogs.com/jasonxuli/p/6802289.html   Go 的原生数据类型可以分为基本类型和高级类型,基本类型主要包含 string, bool, int ...

  9. Golang 中操作 Mongo Update 的方法

    Golang 和 MongoDB 中的 ISODate 时间交互问题 2018年02月27日 11:28:43 独一无二的小个性 阅读数:357 标签: GolangMongoDB时间交互时间转换 更 ...

  10. HTTPS相关知识以及在golang中的应用

    最近简单学习了HTTPS,并在golang中实践了一下,现在把学到的知识记录下来,方便以后查看,如果有幸能帮到有需要的人就更好了,如果有错误欢迎留言指出. 一些简单的概念,可以自行百度百科 HTTPS ...

随机推荐

  1. Gitlab 实现仓库完全迁移

    方法一:最快 gitlab用url导入注意事项看图 方法二 首先需要在新的服务服务器上新建一个项目 然后用 Git Bash 执行以下命令 git clone --mirror 项目原代码仓库地址 / ...

  2. 阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践

    本文由阿里闲鱼技术团队有攸分享,原题"向消息延迟说bybye:闲鱼消息及时到达方案",有修订和改动,感谢作者的分享. 1.引言 IM消息作为闲鱼用户重要的交易咨询工具,核心目标有两 ...

  3. Spring 框架基础

    一.Spring框架 1.框架简介 Spring是一个开源框架,框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架.Spring使用基 ...

  4. spandsp_start_dtmf的bug及修复

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. 之前的文章中介绍过DTMF从2833到inband的转换,其中inband到2833的转换使用了"spandsp_star ...

  5. Linux下本地yum源配置及局域网yum配置

    1.Linux下本地yum源配置 本地yum源依赖于python解析,首先要确保系统的python和yum源安装完成 1.1.本地yum源配置及挂载 上传ISO镜像或使用本机镜像,使用mount命令挂 ...

  6. Matplotlab显示OpenCV读取到的图像

    Matplotlab显示OpenCV读取到的图像 一. 确认图像的数组类型 在使用 OpenCV 的 cv2.imread() 函数读取图像时,第二个参数(标志)决定了图像的读取方式.具体来说,0.1 ...

  7. 浅谈ChatGPT在云计算资源调度的应用

    本文分享自天翼云开发者社区<浅谈ChatGPT在云计算资源调度的应用>,作者:张****兵 一.ChatGPT技术原理 ChatGPT 是基于 GPT(Generative Pre-tra ...

  8. 玩转云端 | 天翼云边缘安全加速平台AccessOne实用窍门之多款产品管理难?一站式平台管理全hold住!

    随着数字化转型深入推进,企业信息化建设成效显著,同时其所面临的安全与性能挑战也日趋复杂,既要确保业务系统的安全性,同时也要提供快速.流畅的用户体验,以提升用户满意度和业务竞争力. 在传统的解决方案中, ...

  9. InfluxDB修改数据存储位置(二进制安装版)

    InfluxDB修改数据存储位置(二进制安装版) 在二进制安装方式下,修改InfluxDB的数据存储位置通常涉及以下几个步骤.以下步骤以InfluxDB 2.x版本为例进行说明,因为InfluxDB ...

  10. [记录点滴] Spark迁移到Flink的几个点

    [记录点滴] Spark迁移到Flink的几个点 0x00 三个问题点 有三个Spark API需要找到对应的Flink API或者替代方法 reduceByKeyAndWindow 函数reduce ...