GoLang 陷阱

2019-05-03 language golang

简单列举一些 GoLang 中容易犯的错误。

数组

数组是值传递

如果需要在函数内修改,建议使用切片而非数组。

package main

import "fmt"

func main() {
        x := [3]int{1, 2, 3}
        y := []int{1, 2, 3}

        func(a [3]int, b []int) {
                a[0] = 7
                fmt.Printf("inner %v\n", a)

                b[0] = 7
                fmt.Printf("inner %v\n", b)
        }(x, y)
        fmt.Printf("outer %v\n", x)
        fmt.Printf("outer %v\n", y)
}

上述的变量中,y 作为切片,在函数内的修改会影响到函数外的变量,因为它修改的是指针。

异常处理

恢复的使用方式

recover() 函数必须在 defer 的函数中调用,不允许通过函数再次嵌套,不能直接调用,否则会失效。

package main

import "fmt"

func main() {
        x := [3]int{1, 2, 3}
        y := []int{1, 2, 3}

        func(a [3]int, b []int) {
                a[0] = 7
                fmt.Printf("inner %v\n", a)

                b[0] = 7
                fmt.Printf("inner %v\n", b)
        }(x, y)
        fmt.Printf("outer %v\n", x)
        fmt.Printf("outer %v\n", y)
}

调度

协程泄露

与很多高级语言类似,GoLang 自带了内存回收的特性,一般内存不会泄露,但可能会出现在 GC 的时候出现卡顿。

但是,对于协程来说就可能会出现泄露,同时该协程引用的对象也无法释放。

package main

import (
        "context"
        "fmt"
)

func main() {
        ctx, cancel := context.WithCancel(context.Background())

        ch := make(chan int)
        go func() {
                for i := 0; ; i++ {
                        // ch <- i
                        select {
                        case <-ctx.Done():
                                return
                        case ch <- i:
                        }
                }
        }()

        for v := range ch {
                fmt.Println(v)
                if v == 5 {
                        cancel()
                        break
                }
        }
}

上述会起一个后台的协程向管道中添加数据,如果工作协程退出,那么后台协程就可能会泄露。其中 context 包是最常用的,可以有效的处理协程之间的嵌套调用。

独占CPU

协程采用的是协作式强占调度,本身不会主动放弃 CPU ,如果在某个协程中死循环,那么就可能会导致其它的协程无法调度。

package main

import (
        "fmt"
        "runtime"
)

func main() {
        runtime.GOMAXPROCS(1)

        go func() {
                for i := 0; i < 10; i++ {
                        fmt.Println(i)
                }
        }()

        //for {} // this will hang forever.
        for {
                runtime.Gosched()
        }
        // OR 'select' for quit signal.
}

有两种规避的方式,在死循环中允许协程重新调度,或者使用阻塞的方式等待退出。