指针陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type user struct {
name string
}

func main() {
tom := &user{
name: "tom",
}
jack := tom
jack.name = "jack"
fmt.Println(tom.name) // jack
}
  1. tom是指向user的指针,name=tom
  2. tom赋值给jack
  3. jack修改name=jack
  4. tom.name也变为jack

struct赋值顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

type user struct {
name string
age int
}

func main() {
tom := &user{
name: fmt.Sprintf("%s", "tom"),
age: 18,
}
fmt.Println(tom)
}

age虽然在name之前,但是因为name是函数fmt.Sprintf的返回值,所以在初始化user的时候,会先处理成员变量是表达式的返回值的。

defer panic return顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
fmt.Println(do())
}
func do() string {
defer fmt.Println("do defer")
fmt.Println("do something")
panic("do panic")
return "do return"
}
//do something
//do defer
//panic: do panic

协程遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中,遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
fmt.Println(do())
}
func do() string {
defer fmt.Println("do defer")
fmt.Println("do something")
return "do return"
}

//do something
//do defer
//do return
  1. defer只对当前协程有效(main可以看作是主协程);
  2. 当panic发生时依然会执行当前(主)协程中已声明的defer,但如果所有defer都未调用recover()进行异常恢复,则会在执行完所有defer后引发整个进程崩溃;
  3. 主动调用os.Exit(int)退出进程时,已声明的defer将不再被执行。

json number反射到interface的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"encoding/json"
"fmt"
"reflect"
)

func main() {
user := `{ "age" : 10}`
m := make(map[string]interface{})
json.Unmarshal([]byte(user), &m)
age, ok := m["age"].(int)
fmt.Println(age, ok) // 0 false
age2, ok2 := m["age"].(float64)
fmt.Println(age2, ok2, reflect.TypeOf(m["age"])) // 10 true float64
}

json number反射到interface上,这时通过断言为int是错误的,此时interface断言是float64。

for循环临时变量和goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"time"
)

func main() {
for i := 0; i < 10; i++ {
go func() {
fmt.Printf("%v ", i) //10 10 10 10 10 10 10 10 10 7
}()
}
time.Sleep(time.Second)
}

i是临时变量,当每个goroutine抢着去打印i的时候,其结果是不确定的,每个gourotine运行到的时候,i当前是什么,就打印什么。

range一个chan的时候需要close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
//close(ch)
}()
for index := range ch {
fmt.Println("index is ", index)
}
}
// index is 0
// index is 1
// index is 2
// index is 3
// index is 4
// fatal error: all goroutines are asleep - deadlock!

Map中key不存在的时候,返回的是对应类型的零值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
x := map[string]int{"one": 0, "three": 3}
if v := x["one"]; v == 0 {
fmt.Println("key one is 0")
}
if v := x["two"]; v == 0 {
fmt.Println("key two is 0")
}
}
//key one is 0
//key two is 0

可以通过if v,ok := x["two"]; ok来判断

代码块内同名变量的修改,不会影响代码块外的值

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
n := 1
{
fmt.Println(n) // 1
n := 2
fmt.Println(n) // 2
}
fmt.Println(n) // 1
}

nil!=nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"reflect"
)

func main() {
var a interface{}
fmt.Println(a) // nil
var b *int
fmt.Println(b) // nil
fmt.Println(reflect.TypeOf(a)) // nil
fmt.Println(reflect.TypeOf(b)) // *nil
fmt.Println(a == b) // false
}

小心nil != nil的陷阱。golang的interface是由两个部分组成的,{Type, Value},a相当于{nil,nil},b相当于{*int,nil},a自然不等于b。

小心类型溢出

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
var i byte
for i = 0; i <= 255; i++ {
fmt.Println(i)
}
}

这里其实会卡住,首先byte本质是uint8,最大值是255,当到达255后,先加1,就会发生溢出,回到0,所以这行代码本质就是个for{}

可以通过context timeout来控制超时

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
package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
defer cancel()
go task(ctx)
for {
}
}
func task(ctx context.Context) {
ch := make(chan struct{}, 0)
go func() {
time.Sleep(time.Second * 2)
ch <- struct{}{}
}()
select {
case <-ch:
fmt.Println("done")
case <-ctx.Done():
fmt.Println("timeout")
return
}
}
//timeout

go抢占式调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
go func() {
for {

}
}()
fmt.Println("let's go")
}
// let's go

在只有一个处理器的情况下,即使一个goroutine是死循环,它也不会一直执行下去

两个无符号数字最好不要相减

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
var a uint8 = 100
var b uint8 = 102
fmt.Println(a - b) // 254
}

最好不要相减,容易得到负数

go的struct是可以比较的

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"fmt"
)
type user struct {
name string
}
func main() {
u1 := user{name : "1"}
u2 := user{name: "1"}
fmt.Println( u1==u2 ) //true
}

map、slice、func不可比较,除非是nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"
type user struct {
name string
}
func main( ) {
u1 := map[string]string{ "name " : "1"}
u2 := map[string]string{ " name " : "1"}
fmt.Println(ul == u2) //invalid operation: u1 == u2 ( map can only be compared to nil )

u3 := [ ]int{}
u4 := [ ]int{}
fmt.Println(u3 == u4) //invalid operation: u3 == u4 ( slice can only be compared to nil )

u5 := func() {}
u6 := func() {}
fmt.Println( u5 == u6) // invalid operation: u5 == u6 ( func can only be compared to nil )
}

注意close掉http body,防止内存泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"io/ioutil"
"net/http"
)

func main( ) {
resp,err := http.Get("http://xx.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
}

注册recover,防止panic导致的程序意外退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
go func() {
defer func() {
if err := recover();err != nil {
log.Println(err)
}
}()
panic("error")
}
select{}
}

go 语句后面的函数调用,其参数会先求值

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
ch1 := make(chan int)
go fmt.Println(<-ch1) //go 语句后面的函数调用,其参数会先求值 所以这里会deadlock
ch1 <- 5
time.Sleep(1 * time.Second)
}

其他

  • 永远不要使用形如 var p*a 声明变量,这会混淆指针声明和乘法运算(参考4.9小节
  • 永远不要在for循环自身中改变计数器变量(参考5.4小节
  • 永远不要在for-range循环中使用一个值去改变自身的值(参考5.4.4小节
  • 永远不要将goto和前置标签一起使用(参考5.6小节
  • 永远不要忘记在函数名(参考第6章)后加括号(),尤其调用一个对象的方法或者使用匿名函数启动一个协程时
  • 永远不要使用new()一个map,一直使用make(参考第8章
  • 当为一个类型定义一个String()方法时,不要使用fmt.Print或者类似的代码(参考10.7小节
  • 永远不要忘记当终止缓存写入时,使用Flush函数(参考12.2.3小节
  • 永远不要忽略错误提示,忽略错误会导致程序奔溃(参考13.1小节
  • 不要使用全局变量或者共享内存,这会使并发执行的代码变得不安全(参考14.1小节
  • println函数仅仅是用于调试的目的