對一段Go語言代碼輸出結果的簡要分析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

年後事情實在是多,各種被催進度,於是好長一段時間未更部落格了,自責中….。今天蹦出來熱熱身^0^!

中午在微博私信中看到一封來自某Gopher的諮詢,他貼了一段代碼,並表示對代碼的輸出結果的不解,希望我能幫他分析一下。他的代碼如下:

//testslicerange.gopackage mainimport (    "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,Go 1.10上面程式與此稍有不同,輸出的item相同,只是前後順序有不同):

onetwothreesixsixsix

雖然這位Gopher並沒有明確說明他的疑惑究竟是什嗎?但從上述的輸出結果來看,他一定是想問:為什麼對data2的迭代輸出的是三個”six”,而不是four、five、six?

好了,我來分析一下。首先,我要對這個程式做個等價變換,變換後的程式源碼如下:

//testslicerange-transform.gopackage mainimport (    "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是一致的:

onetwothreesixsixsix

這樣變換以後,問題是不是豁然開朗了,你可以很清楚地看到使用go關鍵字啟動一個新goroutine時是如何綁定參數的:

- 迭代data1時,由於data1中的元素類型是field指標,因此賦值後v就是元素地址, 每次調用print時傳入的參數(v)實際上也是各個field元素的地址;- 迭代data2時,由於data2中的元素類型是field(非指標),因此賦值後v是元素的copy,每次傳入的&v實際上是v的地址,而不是被copy的元素的地址;

剩下的就是for range常見的那個”坑”的問題(在我的《關於Go,你可能不注意的7件事》一文中有詳盡說明),那就是v在整個for range過程只有一個,data2迭代完成之後,v是元素”six”的copy

這樣,一旦啟動的各個child goroutine在main goroutine執行到Sleep時才被調度執行,那麼最後的三個goroutine在列印&v時,列印的也就都v
中存放的值”six”了。而前三個child goroutine各自傳入的是元素(“one”、”two”、”three”)的地址,列印的就是”one”、”two”、”three”。

那麼原程式如何修改一下才能讓其按期望輸出(“one”、”two”、”three”, “four”, “five”, “six”)呢?我們來改一下:只需將field method的receiver type由*field改為field即可。

// testslicerange1.gopackage mainimport (    "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上的輸出為:

onetwothreefourfivesix

至於為什麼,可以參考我的分析思路,自行分析一下。

著名雲主機服務廠商DigitalOcean發布最新的主機計劃,入門級Droplet配置升級為:1 core CPU、1G記憶體、25G高速SSD,價格5$/月。有使用DigitalOcean需求的朋友,可以開啟這個連結地址:https://m.do.co/c/bff6eed92687 開啟你的DO主機之路。

我的連絡方式:

微博:https://weibo.com/bigwhite20xx
公眾號:iamtonybai
部落格:tonybai.com
github: https://github.com/bigwhite

讚賞:

商務合作方式:撰稿、出書、培訓、線上課程、合夥創業、諮詢、廣告合作。

2018, bigwhite. 著作權.

Related posts:

  1. 關於Go,你可能不注意的7件事
  2. Go與C語言的互操作
  3. Go 1.4中值得關注的幾個變化
  4. 近期遇到的3個Golang代碼問題
  5. Go中的系統Signal處理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.