1、寫出下面代碼輸出內容。
package mainimport ("fmt"
)func main() {defer_call()
}func defer_call() {defer func() { fmt.Println("打印前") }()defer func() { fmt.Println("打印中") }()defer func() { fmt.Println("打印后") }()panic("觸發異常")
}
解析:
defer
關鍵字的實現跟go關鍵字很類似,不同的是它調用的是runtime.deferproc
而不是runtime.newproc
。
在defer
出現的地方,插入了指令call runtime.deferproc
,然后在函數返回之前的地方,插入指令call runtime.deferreturn
。
goroutine的控制結構中,有一張表記錄defer
,調用runtime.deferproc
時會將需要defer的表達式記錄在表中,而在調用runtime.deferreturn
的時候,則會依次從defer表中出棧并執行。
因此,題目最后輸出順序應該是defer
定義順序的倒序。panic
錯誤并不能終止 defer
的執行。
2、 以下代碼有什么問題,說明原因
type student struct {Name stringAge int
}func pase_student() {m := make(map[string]*student)stus := []student{{Name: "zhou", Age: 24},{Name: "li", Age: 23},{Name: "wang", Age: 22},}for _, stu := range stus {m[stu.Name] = &stu}
}
解析:
golang 的 for ... range
語法中,stu
變量會被復用,每次循環會將集合中的值復制給這個變量,因此,會導致最后m
中的map
中儲存的都是stus
最后一個student
的值。
3、下面的代碼會輸出什么,并說明原因
func main() {runtime.GOMAXPROCS(1)wg := sync.WaitGroup{}wg.Add(20)for i := 0; i < 10; i++ {go func() {fmt.Println("i: ", i)wg.Done()}()}for i := 0; i < 10; i++ {go func(i int) {fmt.Println("i: ", i)wg.Done()}(i)}wg.Wait()
}
解析:
這個輸出結果決定來自于調度器優先調度哪個G。從runtime的源碼可以看到,當創建一個G時,會優先放入到下一個調度的runnext
字段上作為下一次優先調度的G。因此,最先輸出的是最后創建的G,也就是9.
func newproc(siz int32, fn *funcval) {argp := add(unsafe.Pointer(&fn), sys.PtrSize)gp := getg()pc := getcallerpc()systemstack(func() {newg := newproc1(fn, argp, siz, gp, pc)_p_ := getg().m.p.ptr()//新創建的G會調用這個方法來決定如何調度runqput(_p_, newg, true)if mainStarted {wakep()}})
}
...if next {retryNext:oldnext := _p_.runnext//當next是true時總會將新進來的G放入下一次調度字段中if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {goto retryNext}if oldnext == 0 {return}// Kick the old runnext out to the regular run queue.gp = oldnext.ptr()}
4、下面代碼會輸出什么?
type People struct{}func (p *People) ShowA() {fmt.Println("showA")p.ShowB()
}
func (p *People) ShowB() {fmt.Println("showB")
}type Teacher struct {People
}func (t *Teacher) ShowB() {fmt.Println("teacher showB")
}func main() {t := Teacher{}t.ShowA()
}
解析:
輸出結果為showA
、showB
。golang 語言中沒有繼承概念,只有組合,也沒有虛方法,更沒有重載。因此,*Teacher
的 ShowB
不會覆寫被組合的 People
的方法。
5、下面代碼會觸發異常嗎?請詳細說明
func main() {runtime.GOMAXPROCS(1)int_chan := make(chan int, 1)string_chan := make(chan string, 1)int_chan <- 1string_chan <- "hello"select {case value := <-int_chan:fmt.Println(value)case value := <-string_chan:panic(value)}
}
解析:
結果是隨機執行。golang 在多個case
可讀的時候會公平的選中一個執行。
6、下面代碼輸出什么?
func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret
}func main() {a := 1b := 2defer calc("1", a, calc("10", a, b))a = 0defer calc("2", a, calc("20", a, b))b = 1
}
解析:
輸出結果為:
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
defer
在定義的時候會計算好調用函數的參數,所以會優先輸出10
、20
兩個參數。然后根據定義的順序倒序執行。
7、請寫出以下輸入內容
func main() {s := make([]int, 5)s = append(s, 1, 2, 3)fmt.Println(s)
}
解析:
輸出為 0 0 0 0 0 1 2 3
。
make
在初始化切片時指定了長度,所以追加數據時會從len(s)
位置開始填充數據。
8、下面的代碼有什么問題?
type UserAges struct {ages map[string]intsync.Mutex
}func (ua *UserAges) Add(name string, age int) {ua.Lock()defer ua.Unlock()ua.ages[name] = age
}func (ua *UserAges) Get(name string) int {if age, ok := ua.ages[name]; ok {return age}return -1
}
解析:
在執行 Get方法時可能被panic。
雖然有使用sync.Mutex做寫鎖,但是map是并發讀寫不安全的。map屬于引用類型,并發讀寫時多個協程見是通過指針訪問同一個地址,即訪問共享變量,此時同時讀寫資源存在競爭關系。會報錯誤信息:“fatal error: concurrent map read and map write”。
因此,在 Get
中也需要加鎖,因為這里只是讀,建議使用讀寫鎖 sync.RWMutex
。
9、下面的迭代會有什么問題?
func (set *threadSafeSet) Iter() <-chan interface{} {ch := make(chan interface{})go func() {set.RLock()for elem := range set.s {ch <- elem}close(ch)set.RUnlock()}()return ch
}
解析:
默認情況下 make
初始化的 channel
是無緩沖的,也就是在迭代寫時會阻塞。
10、以下代碼能編譯過去嗎?為什么?
package mainimport ("fmt"
)type People interface {Speak(string) string
}type Student struct{}func (stu *Student) Speak(think string) (talk string) {if think == "bitch" {talk = "You are a good boy"} else {talk = "hi"}return
}func main() {var peo People = Student{}think := "bitch"fmt.Println(peo.Speak(think))
}
解析:
編譯失敗,值類型 Student{}
未實現接口People
的方法,不能定義為 People
類型。
在 golang 語言中,Student
和 *Student
是兩種類型,第一個是表示 Student
本身,第二個是指向 Student
的指針。
11、以下代碼打印出來什么內容,說出為什么。。。
package mainimport ("fmt"
)type People interface {Show()
}type Student struct{}func (stu *Student) Show() {}func live() People {var stu *Studentreturn stu
}func main() {if live() == nil {fmt.Println("AAAAAAA")} else {fmt.Println("BBBBBBB")}
}
解析:
跟上一題一樣,不同的是*Student
的定義后本身沒有初始化值,所以 *Student
是 nil
的,但是*Student
實現了 People
接口,接口不為 nil
。