关于程序员:正确规避常见的-Go-并发陷阱

42次阅读

共计 1407 个字符,预计需要花费 4 分钟才能阅读完成。

作为一门并发性能优越的语言,Go 尽管升高了协程并发开发的难度,但也存在一些「并发陷阱」,这就须要咱们在开发时额定留神。

咱们再来介绍与 Go 并发相干的几个小技巧,帮忙你躲避 Go 并发开发的一些陷阱。

闭包传递参数的问题
首先是循环并发时闭包传递参数的问题,如下谬误例子所示:

func main() {
for i := 0 ; i < 5 ; i++{
go func() {
fmt.Println(“current i is “, i)
}()
}
time.Sleep(time.Second)
}
这段代码极有可能的输入为:

current i is 5
current i is 5
current i is 5
current i is 5
current i is 5
这是因为 i 应用的地址空间在循环中被复用,在 goroutine 执行时,i 的值可能在被主 goroutine 批改,而此时其余 goroutine 也在读取应用,从而导致了并发谬误。针对这种谬误能够通过「复制拷贝」或者「传参拷贝」的形式躲避,如下所示:

func main() {

for i := 0 ; i < 5 ; i++{go func(v int) {fmt.Println("current i is", v)
    }(i)
}
time.Sleep(time.Second)

}
panic 异样
上一篇介绍 panic 时咱们理解到 panic 异样的呈现会导致 Go 程序的解体。但其实即便 panic 是呈现在其余启动的子 goroutine 中,也会导致 Go 程序的解体退出,同时 panic 只能捕捉 goroutine 本身的异样,因而「对于每个启动的 goroutine,都须要在入口处捕捉 panic,并尝试打印堆栈信息并进行异样解决,从而防止子 goroutine 的 panic 导致整个程序的解体退出」。如上面的例子所示:

func RecoverPanic() {

// 从 panic 中复原并打印栈信息

if e := recover(); e != nil {
buf := make([]byte, 1024)
buf = buf[:runtime.Stack(buf, false)]
fmt.Printf(“[PANIC]%v\n%s\n”, e, buf)
}
}
func main() {
for i:= 0 ; i < 5 ; i++{
go func() {

        // defer 注册 panic 捕捉函数

defer RecoverPanic()
dothing()
}()
}
}
超时管制
最初一个技巧是要「长于联合应用 select、timer 和 context 进行超时管制」。在 goroutine 中进行一些耗时较长的操作,最好都加上超时 timer,在并发的时候也要传递 context,这样在勾销的时候就不会有脱漏,进而达到回收 goroutine 的目标,防止内存透露的产生。如上面的例子所示,通过 select 同时监听工作和定时器状态,在定时器达到而工作未实现之时,提前结束工作,清理资源并返回。

select {
// do logic process
case msg <- input:
….
// has been canceled
case <-ctx.Done():

// ... 资源清理
return

// 2 second timeout
case <-time.After(time.Second * 2)

// ... 资源清理
return

default:
}
小结
本文咱们次要介绍了 Go 中常见的一些并发开发技巧。笔者也只是抛砖引玉,提出了并发开发技巧的三个技巧,其实不仅仅是 Go 并发,大家在日常开发中须要留神总结各个方面的开发技巧。

正文完
 0