返回首页 - Notes - 2017

Go 容易让人困惑的知识点


异常对 defer 执行的影响

情形一

package main

import (
  "fmt"
)

func main() {
  defer func() { fmt.Println("AAA") }()
  defer func() { fmt.Println("BBB") }()
  defer func() { fmt.Println("CCC") }()

  panic("异常")
}

// 输出结果

// CCC
// BBB
// AAA
// panic: 异常

panicdefer 执行后被调用,不会影响到 defer 的执行

情形二

package main

import (
  "fmt"
  "log"
)

func main() {
  defer func() { fmt.Println("AAA") }()
  defer func() { fmt.Println("BBB") }()
  defer func() { fmt.Println("CCC") }()

  log.Fatal("异常")
}

// 输出结果

// 2017/07/12 11:52:30 异常

log.Fataldefer 执行前被调用,且终止了后续 defer 的执行

情形三

package main

import (
  "fmt"
  "log"
)

func main() {
  defer func() { fmt.Println("AAA") }()
  defer func() { fmt.Println("BBB") }()
  defer func() { fmt.Println("CCC") }()

  panic("异常")
  log.Fatal("异常")
}

// 输出结果

// CCC
// BBB
// AAA
// panic: 异常

程序在遇到 panic 后就开始执行 defer,然后执行 panic 本身,最后程序终止执行,后面的语句没有机会运行

情形四

package main

import (
  "fmt"
  "log"
)

func main() {
  defer func() { fmt.Println("AAA") }()
  defer func() { fmt.Println("BBB") }()
  defer func() { fmt.Println("CCC") }()

  log.Fatal("异常")
  panic("异常")
}

// 输出结果

// 2017/07/12 12:05:13 异常

程序在执行完 log.Fatal 后就终止了程序的运行,连 panicdefer 都没机会被执行

情形五

package main

import (
  "fmt"
  "log"
)

func main() {
  defer func() { fmt.Println("AAA") }()
  defer func() { fmt.Println("BBB") }()
  defer func() { fmt.Println("CCC") }()

  fmt.Println("xxx")
  panic("异常")
  fmt.Println("yyy")
  log.Fatal("异常")
}

// 输出结果

// xxx
// CCC
// BBB
// AAA
// panic: 异常

以上面的代码为例,总结下这个知识点:

  1. log.Fatal 会打印异常日志,并立即终止整个程序,panicdefer 都被跳过
  2. panic 和正常语句一样顺序执行,但一旦遇到 panic,程序就会去把 defer 都调用一遍,然后调用 panic 本身,最后终止程序运行
  3. log.Fatal 影响 defer
  4. panic 不影响 defer,且肯定会调用 defer
  5. log.Fatalpanic 都会终止程序的运行

range 迭代中临时变量的地址

有下面的代码

package main

import "fmt"

type User struct {
  Name string
  Age  int
}

func main() {
  m := make(map[string]*User)
  users := []User{
    {"zhangsan", 18},
    {"lisi", 19},
    {"wangwu", 20},
  }

  for _, user := range users {
    m[user.Name] = &user
  }

  fmt.Println(m)
}

// 输出结果

// map[zhangsan:0xc42007c060 lisi:0xc42007c060 wangwu:0xc42007c060]

输出结果看出问题了吗?三个项的值都是一样的,如果打印各个项的 Name 属性,会输出一样的结果

问题在于,range 迭代时,user 这个临时变量的地址是固定的,取临时变量的地址存进 map,其实存的都是一样的值

修正这个问题很简单,再加个临时变量储存下 range 临时变量的值即可

代码如下

package main

import "fmt"

type User struct {
  Name string
  Age  int
}

func main() {
  m := make(map[string]*User)
  users := []User{
    {"zhangsan", 18},
    {"lisi", 19},
    {"wangwu", 20},
  }

  for _, user := range users {
    val := user // 加这一行
    m[user.Name] = &val
  }

  fmt.Println(m)
}

// 输出结果

// map[zhangsan:0xc42007c060 lisi:0xc42007c080 wangwu:0xc42007c0a0]

在循环中使用 Go

有下面的代码

package main

import (
  "fmt"
  "runtime"
  "sync"
  "time"
)

func main() {
  // 故意限制只使用一个逻辑处理器
  runtime.GOMAXPROCS(1)

  var wg sync.WaitGroup
  wg.Add(10)

  for i := 0; i < 5; i++ {
    go func() {
      fmt.Println("a ", i)
      wg.Done()
    }()
  }

  for i := 0; i < 5; i++ {
    go func(i int) {
      fmt.Println("b ", i)
      wg.Done()
    }(i)
  }

  // 暂停一会
  time.Sleep(100 * time.Millisecond)

  wg.Wait()
}

脑算下会输出什么?注意逻辑处理器被限制为只使用一个,还有第一个循环和第二个循环的区别

我们故意暂停了 100 毫秒,等待循环里面的 Go 程都创建完

第一个循环没有把临时变量传进 Go 程里面,所以等循环执行完,临时变量 i 已经是 5 了,而等这部分 Go 程跑起来时用的就是 5

第二个循环把临时变量传进了 Go 程,所以这部分 Go 程每一个拿到的 i 值都不一样

所以最终的输出结果是

a  5
a  5
a  5
a  5
a  5
b  0
b  1
b  2
b  3
b  4

类型嵌套下的方法调用

有下面的代码

package main

import "fmt"

type User struct{}
type Admin struct{ User }

func (u *User) HelloA() {
  fmt.Println("HelloA from User")
  u.HelloB()
}

func (u *User) HelloB() {
  fmt.Println("HelloB from User")
}

func (a *Admin) HelloB() {
  fmt.Println("HelloB from Admin")
}

func main() {
  a := Admin{}
  a.HelloA()
}

// 输出结果

// HelloA from User
// HelloB from User

在类型 Admin 里面嵌套了类型 User

Admin 类型的变量上调用自身不存在的方法时,会去嵌套的类型进行查找,所以调用 HelloA 自动调用了 User 类型上的 HelloA

而在 HelloA 里再行调用 HelloB 时,是在 User 类型的变量上调用的,和 Admin 不再有关系

如果将 User 类型上的 HelloB 方法注释掉,这个程序就会编译出错,因为 u.HelloB() 找不到方法来调用


defer 的执行顺序

有下面的代码

package main

import "fmt"

func calc(flag string, a, b int) int {
  c := a + b
  fmt.Println(flag, a, b, c)
  return c
}

func main() {
  a := 1
  b := 2
  defer calc("a", a, calc("x", a, b))
  a = 0
  defer calc("b", a, calc("y", a, b))
  b = 1
}

这段代码的执行有点意思

我们都知道 defer 是在函数结束前被调用的,且调用顺序是后面的先执行,所以可以肯定先输出 b ...,再输出 a ...

但是,那 x ...y ... 在什么位置输出呢?

真相是这样:

  1. 程序顺序执行,defer 语句会被标记起来,留待函数返回前调用
  2. 但是在标记 defer 语句时会先把它用到的变量的值、表达式的值,都先计算出来
  3. 所以,像 defer calc("a", a, calc("x", a, b)) 这样的语句,会先执行 calc("x", a, b) 把这个函数调用的值算出来
  4. 而且,标记 defer 时,会将变量的值固定下来,不会受到后续修改的影响

基于上述流程,上面的 main 函数,实质上约等于下面的代码:

func main() {
  c := calc("x", 1, 2)
  defer calc("a", 1, c)

  d := calc("y", 0, 2)
  defer calc("b", 0, d)
}

map 的初始化

有下面的代码

package main

import "fmt"

type User struct {
  data map[string]int
}

func (u *User) Add(name string, age int) {
  u.data[name] = age
}

func (u *User) Get(name string) int {
  if age, ok := u.data[name]; ok {
    return age
  }
  return -1
}

func main() {
  u := User{}

  u.Add("zhangsan", 18)
  fmt.Println(u.Get("zhangsan"))
}

上面的代码编译会报错,问题在调用 Add 方法时,u.data 的值还未初始化,没法添加键值对

要修正上面的代码,只需将 u := User{} 改成 u := User{data: make(map[string]int)} 即可

或者在 u := User{} 下面添加一行:u.data = make(map[string]int)


date:2017-07-12