对一段Go语言代码输出结果的简要分析

Posted TonyBai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对一段Go语言代码输出结果的简要分析相关的知识,希望对你有一定的参考价值。

年后事情实在是多,各种被催进度,于是好长一段时间未更博客了,自责中….。今天蹦出来热热身^0^!


中午在微博私信中看到一封来自某Gopher的咨询,他贴了一段代码,并表示对代码的输出结果的不解,希望我能帮他分析一下。他的代码如下:


//testslicerange.go

package main



import (

   "fmt"

   "time"

)



type field struct {

   name string

}



func (p *field) print() {

   fmt.Println(p.name)

}



func main() {

   data1 := []*field{{"one"}, {"two"},
              {"three"}}

   for _, v := range data1 {

       go v.print()

   }



   data2 := []field{{"four"}, {"five"},
           {"six"}}

   for _, v := range data2 {

       go v.print()

   }



   time.Sleep(3 * time.Second)

}


在go playground上,其输出结果为(在我的多核mac,上面程序与此稍有不同,输出的item相同,只是前后顺序有不同):


one

two

three

six

six

six


虽然这位Gopher并没有明确说明他的疑惑究竟是什么?但从上述的输出结果来看,他一定是想问:为什么对data2的迭代输出的是三个”six”,而不是four、five、six?


好了,我来分析一下。首先,我要对这个程序做个等价变换,变换后的程序源码如下:


//testslicerange-transform.go

package main



import (

   "fmt"

   "time"

)



type field struct {

   name string

}



func print(p *field) {

   fmt.Println(p.name)

}



func main() {



   data1 := []*field{{"one"}, {"two"}, 
         {"three"}}

   for _, v := range data1 {

       go print(v)

   }



   data2 := []field{{"four"}, {"five"},
          {"six"}}

   for _, v := range data2 {

       go print(&v)

   }



   time.Sleep(3 * time.Second)

}


这里我把field结构体的method:print,换成了普通的以field指针作为第一个参数的函数print,这个变换是等价的,因为go中的method本质上就是以method的receiver作为第一个参数的普通function,即:


instance.method(x,y) 
 <=> function(instance, x,y)


因此,执行上述的变换后的testslicerange-transform.go,得到的结果与testslicerange.go是一致的:


one

two

three

six

six

six


这样变换以后,问题是不是豁然开朗了,你可以很清楚地看到使用go关键字启动一个新goroutine时是如何绑定参数的:



剩下的就是for range常见的那个”坑”的问题(在我的《关于Go,你可能不注意的7件事》一文中有详尽说明),那就是v在整个for range过程只有一个,data2迭代完成之后,v是元素”six”的copy。



那么原程序如何修改一下才能让其按期望输出(“one”、”two”、”three”, “four”, “five”, “six”)呢?我们来改一下:只需将field method的receiver type由*field改为field即可。


// testslicerange1.go

package main



import (

   "fmt"

   "time"

)



type field struct {

   name string

}



func (p field) print() {

   fmt.Println(p.name)

}



func main() {



   data1 := []*field{{"one"}, {"two"}, 
          {"three"}}

   for _, v := range data1 {

       go v.print()

   }



   data2 := []field{{"four"}, {"five"},
         {"six"}}

   for _, v := range data2 {

       go v.print()

   }



   time.Sleep(3 * time.Second)

}


上述程序在go playground上的输出为:


one

two

three

four

five

six


至于为什么,可以参考我的分析思路,自行分析一下。





我的联系方式:


微博:https://weibo.com/bigwhite20xx

博客:tonybai.com

github: https://github.com/bigwhite


戳下面的原文阅读,更有料


以上是关于对一段Go语言代码输出结果的简要分析的主要内容,如果未能解决你的问题,请参考以下文章

14. Go 语言编译与工具

聊聊go语言逃逸分析

聊聊go语言逃逸分析

聊聊go语言逃逸分析

Go语言之装饰器

对一段C语言服务器代码的详解