本文即Go语言的那些坑三。
不要对Go并发函数的执行时机做任何假设
请看下列的列子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import ( "fmt" "runtime" "time" ) func main(){ names := []string{"lily", "yoyo", "cersei", "rose", "annei"} for _, name := range names{ go func(){ fmt.Println(name) }() } runtime.GOMAXPROCS(1) runtime.Gosched() }
|
请问输出什么?
答案:
1 2 3 4 5
| annei annei annei annei annei
|
为什么呢?是不是有点诧异?
输出的都是“annei”,而“annei”又是“names”的最后一个元素,那么也就是说程序打印出了最后一个元素的值,而name对于匿名函数来讲又是一个外部的值。因此,我们可以做一个推断:虽然每次循环都启用了一个协程,但是这些协程都是引用了外部的变量,当协程创建完毕,再执行打印动作的时候,name的值已经不知道变为啥了,因为主函数协程也在跑,大家并行,但是在此由于names数组长度太小,当协程创建完毕后,主函数循环早已结束,所以,打印出来的都是遍历的names最后的那一个元素“annei”。
如何证实以上的推断呢?
其实很简单,每次循环结束后,停顿一段时间,等待协程打印当前的name便可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import ( "fmt" "runtime" "time" ) func main(){ names := []string{"lily", "yoyo", "cersei", "rose", "annei"} for _, name := range names{ go func(){ fmt.Println(name) }() time.Sleep(time.Second) } runtime.GOMAXPROCS(1) runtime.Gosched() }
|
打印结果:
1 2 3 4 5
| lily yoyo cersei rose annei
|
以上我们得出一个结论,不要对“go函数”的执行时机做任何的假设,除非你确实能做出让这种假设成为绝对事实的保证。
假设T类型的方法上接收器既有T
类型的,又有*T
指针类型的,那么就不可以在不能寻址的T值上调用*T
接收器的方法
请看代码,试问能正常编译通过吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import ( "fmt" ) type Lili struct{ Name string } func (Lili *Lili) fmtPointer(){ fmt.Println("poniter") } func (Lili Lili) fmtReference(){ fmt.Println("reference") } func main(){ li := Lili{} li.fmtPointer() }
|
答案:
感觉有点诧异,请接着看以下的代码,试问能编译通过?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import ( "fmt" ) type Lili struct{ Name string } func (Lili *Lili) fmtPointer(){ fmt.Println("poniter") } func (Lili Lili) fmtReference(){ fmt.Println("reference") } func main(){ Lili{}.fmtPointer() }
|
答案:
1 2 3
| 不能编译通过。 “cannot call pointer method on Lili literal” “cannot take the address of Lili literal”
|
是不是有点奇怪?这是为什么呢?其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是Lili,因此可以调用Lili的方法。