[英]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.