簡體   English   中英

如何理解goroutine的這種行為?

[英]How to understand this behavior of goroutine?

package main
import (  
    "fmt"
    "time"
)
type field struct {  
    name string
}
func (p *field) print() {  
    fmt.Println(p.name)
}
func main() {  
    data := []field{ {"one"},{"two"},{"three"} }
    for _,v := range data {
        go v.print()
    }
    <-time.After(1 * time.Second)
}

為什么此代碼以任何順序打印3個“三”而不是“一個”,“兩個”,“三個”?

有一場數據競賽。

評估goroutine函數的參數時,代碼隱式獲取變量v地址。 請注意,調用v.print()調用(&v).print()簡寫

該循環更改變量v的值。

當goroutines執行時, v就是循環的最后一個值。 不能保證。 它可以按預期執行。

使用競速檢測器運行程序非常有用且容易。 該數據爭用由檢測器檢測並報告。

一種解決方法是創建另一個變量,范圍位於循環內部:

for _, v := range data {
    v := v        // short variable declaration of new variable `v`.
    go v.print()
}

進行此更改后,在評估goroutine的參數時將使用內部變量v的地址。 循環的每次迭代都有一個唯一的內部變量v

解決該問題的另一種方法是使用切片指針:

data := []*field{ {"one"},{"two"},{"three"} } // note '*'
for _, v := range data {
    go v.print()
}

進行此更改后,切片中的各個指針將傳遞給goroutine,而不是范圍變量v的地址。

另一個解決方法是使用slice元素的地址:

data := []field{ {"one"},{"two"},{"three"} } // note '*'
for i:= range data {
    v := &data[i]
    go v.print()
}

由於指針值通常與具有指針接收器的類型一起使用,因此在實踐中通常不會出現這種細微問題。 由於field具有指針接收器,因此對於問題中的data類型,通常使用[]*field而不是[]field

如果goroutine函數在匿名函數中,則避免該問題的常用方法是將范圍變量作為參數傳遞給匿名函數:

for _, v := range data {
  go func(v field) {
    v.print() // take address of argument v, not range variable v.
  }(v)
}

因為問題中的代碼尚未對goroutine使用匿名函數,所以此答案中使用的第一種方法更為簡單。

如上所述,有一個競爭條件,其結果取決於不同流程的延遲,並且定義不明確且不可預測。 例如,如果添加time.Sleep(1*time.Seconds) ,則可能會得到正確的結果。 因為通常goroutine的打印速度超過1秒,並且將具有正確的變量v但這是一種非常糟糕的方法。

Golang有一個特殊的種族檢測器工具,可以幫助發現這種情況。 我建議在閱讀testing時閱讀它。 絕對值得。

還有另一種方法-在goroutine開始時顯式傳遞變量值:

for _, v := range data {
     go func(iv field) {
            iv.print()
      }(v)
 }

這里v將在每次迭代時復制到iv (“內部v”),並且每個goroutine將使用正確的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM