变量&类型相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func main () { var a = []interface {}{1 , 2 , 3 } fmt.Println(a) fmt.Println(a...) } func main () { x := [3 ]int {1 , 2 , 3 } func (arr [3]int ) { arr[0 ] = 7 fmt.Println(arr) }(x) fmt.Println(x) } func Foo () (err error) { if err := Bar(); err != nil { return } return } func main () { m := map [string ]string { "1" : "1" , "2" : "2" , "3" : "3" , } for k, v := range m { println (k, v) } }
调度相关 GMP模型-GoRoutine, Machine, Processor
G(Goroutine):代表Go 协程Goroutine,存储了 Goroutine 的执行栈信息、Goroutine 状态以及 Goroutine 的任务函数等。G的数量无限制,理论上只受内存的影响,创建一个 G 的初始栈大小为2-4K,配置一般的机器也能简简单单开启数十万个 Goroutine ,而且Go语言在 G 退出的时候还会把 G 清理之后放到 P 本地或者全局的闲置列表 gFree 中以便复用。
M(Machine): Go 对操作系统线程(OS thread)的封装,可以看作操作系统内核线程,想要在 CPU 上执行代码必须有线程,通过系统调用 clone 创建。M在绑定有效的 P 后,进入一个调度循环,而调度循环的机制大致是从 P 的本地运行队列以及全局队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M,如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础。M的数量有限制,默认数量限制是 10000,可以通过 debug.SetMaxThreads() 方法进行设置,如果有M空闲,那么就会回收或者睡眠。
P(Processor):虚拟处理器,M执行G所需要的资源和上下文,只有将 P 和 M 绑定,才能让 P 的 runq 中的 G 真正运行起来。P 的数量决定了系统内最大可并行的 G 的数量,**P的数量受本机的CPU核数影响,可通过环境变量$GOMAXPROCS或在runtime.GOMAXPROCS()来设置,默认为CPU核心数。
基于信号的抢占式调度 真正的抢占式调度是基于信号完成的,所以也称为“异步抢占”。不管协程有没有意愿主动让出 cpu 运行权,只要某个协程执行时间过长,就会发送信号强行夺取 cpu 运行权。
M 注册一个 SIGURG 信号的处理函数:sighandler sysmon启动后会间隔性的进行监控,最长间隔10ms,最短间隔20us。如果发现某协程独占P超过10ms,会给M发送抢占信号 M 收到信号后,内核执行 sighandler 函数把当前协程的状态从_Grunning正在执行改成 _Grunnable可执行,把抢占的协程放到全局队列里,M继续寻找其他 goroutine 来运行 被抢占的 G 再次调度过来执行时,会继续原来的执行流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { runtime.GOMAXPROCS(1 ) go func () { for i := 0 ; i < 10 ; i++ { fmt.Println(i) } }() for { runtime.Gosched() } select {} }
闭包相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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) } }
defer相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { for i := 0 ; i < 5 ; i++ { f, err := os.Open("/path/to/file" ) if err != nil { log.Fatal(err) } defer f.Close() } } 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() }() } }
error判定相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type MyError struct { code int msg string } func (e *MyError) Error () string { return fmt.Sprintf("code:%d,msg:%v" , e.code, e.msg) } func returnsError () error { var p *MyError = nil if bad() { p = ErrBad } return p }
切片相关 slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,使用多个 goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致; slice在并发执行中不会报错,但是数据会丢失
切片append时,会发生变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ]...) headerMap[name] = data[:1 ] } }
多线程相关 map、slice等线程不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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) }
recover相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func Foo () (err error) { if err := Bar(); err != nil { return } return } func main () { defer func () { recover () }() panic (1 ) }
内存相关 逃逸机制
编译器会根据变量是否被外部引用来决定是否逃逸: 如果函数外部没有引用,则优先放到栈中; 如果函数外部存在引用,则必定放到堆中; 如果栈上放不下,则必定放到堆上;
总结
栈上分配内存比在堆中分配内存效率更高 栈上分配的内存不需要 GC 处理,而堆需要 逃逸分析目的是决定内分配地址是栈还是堆 逃逸分析在编译阶段完成
goroutine泄漏 泄露原因
Goroutine 内进行channel/mutex 等读写操作被一直阻塞。
Goroutine 内的业务逻辑进入死循环,资源一直无法释放。
Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 } } } 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 } } }