当前位置:网站首页>Go 语言中常见的坑
Go 语言中常见的坑
2022-08-01 20:27:00 【云满笔记】
这里填写标题
1. Go 语言中常见的坑
这里列举的 Go 语言常见坑都是符合 Go 语言语法的,可以正常的编译,但是可能是运行结果错误,或者是有资源泄漏的风险。
1.1. 可变参数是空接口类型
当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。
func main() {
var a = []interface{
}{
1, 2, 3}
fmt.Println(a)
fmt.Println(a...)
}
不管是否展开,编译器都无法发现错误,但是输出是不同的:
[1 2 3]
1 2 3
1.2. 数组是值传递
在函数调用参数中,数组是值传递,无法通过修改数组类型的参数返回结果。
func main() {
x := [3]int{
1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
必要时需要使用切片。
1.3. map 遍历是顺序不固定
map 是一种 hash 表实现,每次遍历的顺序都可能不一样。
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
1.4. 返回值被屏蔽
在局部作用域中,命名的返回值内同名的局部变量屏蔽:
func Foo() (err error) {
if err := Bar(); err != nil {
return
}
return
}
1.5. recover 必须在 defer 函数中运行
recover 捕获的是祖父级调用时的异常,直接调用时无效:
func main() {
recover()
panic(1)
}
直接 defer 调用也是无效:
func main() {
defer recover()
panic(1)
}
defer 调用时多层嵌套依然无效:
func main() {
defer func() {
func() {
recover() }()
}()
panic(1)
}
必须在 defer 函数中直接调用才有效:
func main() {
defer func() {
recover()
}()
panic(1)
}
1.6. main 函数提前退出
后台 Goroutine 无法保证完成任务。
func main() {
go println("hello")
}
1.7. 通过 Sleep 来回避并发中的问题
休眠并不能保证输出完整的字符串:
func main() {
go println("hello")
time.Sleep(time.Second)
}
类似的还有通过插入调度语句:
func main() {
go println("hello")
runtime.Gosched()
}
1.8. 独占 CPU 导致其它 Goroutine 饿死
Goroutine 是协作式抢占调度,Goroutine 本身不会主动放弃 CPU:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
} // 占用 CPU
}
解决的方法是在 for 循环加入 runtime.Gosched() 调度函数:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
runtime.Gosched()
}
}
或者是通过阻塞的方式避免 CPU 占用:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
os.Exit(0)
}()
select{
}
}
1.9. 不同 Goroutine 之间不满足顺序一致性内存模型
因为在不同的 Goroutine, main 函数中无法保证能打印出 hello, world:
var msg string
var done bool
func setup() {
msg = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
println(msg)
}
解决的办法是用显式同步:
var msg string
var done = make(chan bool)
func setup() {
msg = "hello, world"
done <- true
}
func main() {
go setup()
<-done
println(msg)
}
msg 的写入是在 channel 发送之前,所以能保证打印 hello, world
1.10. 闭包错误引用同一个变量
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
改进的方法是在每轮迭代中生成一个局部变量:
func main() {
for i := 0; i < 5; i++ {
i := i
defer func() {
println(i)
}()
}
}
或者是通过函数参数传入:
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
1.11. 在循环内部执行 defer 语句
defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放:
func main() {
for i := 0; i < 5; i++ {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
}
解决的方法可以在 for 中构造一个局部函数,在局部函数内部执行 defer:
func main() {
for i := 0; i < 5; i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}()
}
}
1.12. 切片会导致整个底层数组被锁定
切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = data[:1]
}
// do some thing
}
解决的方法是将结果克隆一份,这样可以释放底层的数组:
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = append([]byte{
}, data[:1]...)
}
// do some thing
}
1.13. 空指针和空接口不等价
比如返回了一个错误指针,但是并不是空的 error 接口:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
1.14. 内存地址会变化
Go 语言中对象的地址可能发生变化,因此指针不能从其它非指针类型的值生成:
func main() {
var x int = 42
var p uintptr = uintptr(unsafe.Pointer(&x))
runtime.GC()
var px *int = (*int)(unsafe.Pointer(p))
println(*px)
}
当内存发送变化的时候,相关的指针会同步更新,但是非指针类型的 uintptr 不会做同步更新。
同理 CGO 中也不能保存 Go 对象地址。
1.15. Goroutine 泄露
Go 语言是带内存自动回收的特性,因此内存一般不会泄漏。但是 Goroutine 确存在泄漏的情况,同时泄漏的 Goroutine 引用的内存同样无法被回收。
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()
for v := range ch {
fmt.Println(v)
if v == 5 {
break
}
}
}
上面的程序中后台 Goroutine 向管道输入自然数序列,main 函数中输出序列。但是当 break 跳出 for 循环的时候,后台 Goroutine 就处于无法被回收的状态了。
我们可以通过 context 包来避免这个问题:
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
当 main 函数在 break 跳出循环时,通过调用 cancel() 来通知后台 Goroutine 退出,这样就避免了 Goroutine 的泄漏。
边栏推荐
- Win10, the middle mouse button cannot zoom in and out in proe/creo
- [Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18
- 面试突击70:什么是粘包和半包?怎么解决?
- [Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)
- useful website
- 数据库单字段存储多个标签(位移操作)
- Interview assault 70: what is the glue bag and a bag?How to solve?
- 实用新型专利和发明专利的区别?秒懂!
- 第56章 业务逻辑之物流/配送实体定义
- Wildcard SSL/TLS certificate
猜你喜欢

乐观锁批量跟新 纯SQL

第55章 业务逻辑之订单、支付实体定义

第58章 结构、纪录与类

终于有人把AB实验讲明白了

The graphic details Eureka's caching mechanism/level 3 cache

数字孪生北京故宫,元宇宙推进旅游业进程

第56章 业务逻辑之物流/配送实体定义

Use WeChat official account to send information to designated WeChat users

如何记录分析你的炼丹流程—可视化神器Wandb使用笔记【1】

【kali-信息收集】(1.2)SNMP枚举:Snmpwalk、Snmpcheck;SMTP枚举:smtp-user-enum
随机推荐
研究生新同学,牛人看英文文献的经验,值得你收藏
密码学的基础:X.690和对应的BER CER DER编码
57: Chapter 5: Develop admin management services: 10: Develop [get files from MongoDB's GridFS, interface]; (from GridFS, get the SOP of files) (Do not use MongoDB's service, you can exclude its autom
如何记录分析你的炼丹流程—可视化神器Wandb使用笔记【1】
环境变量,进程地址空间
New graduate students, great experience in reading English literature, worthy of your collection
Convolutional Neural Network (CNN) mnist Digit Recognition - Tensorflow
面试突击70:什么是粘包和半包?怎么解决?
【torch】张量乘法:matmul,einsum
瀚高数据导入
[Personal work] Wireless network image transmission module
【个人作品】记之-串口日志记录工具
使用Huggingface在矩池云快速加载预训练模型和数据集
任务调度线程池-应用定时任务
WhatsApp group sending actual combat sharing - WhatsApp Business API account
我的驾照考试笔记(3)
使用微信公众号给指定微信用户发送信息
[Energy Conservation Institute] Ankerui Food and Beverage Fume Monitoring Cloud Platform Helps Fight Air Pollution
面试突击70:什么是粘包和半包?怎么解决?
乐观锁批量跟新 纯SQL