[英]How to use a method as a goroutine function
我有這個代碼。 我希望輸出:
hello : 1
world : 2
但它輸出:
world : 2
world : 2
我的代碼有問題嗎?
package main
import (
"fmt"
"time"
)
type Task struct {
name string
data int32
}
func (this *Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
func main() {
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
go task.PrintData()
}
time.Sleep(time.Second * 5000)
}
由於PrintData
是指針接收器而task
是值,因此編譯器在進行方法調用時會自動獲取task
的地址。 結果調用與(&task).PrintData()
。
通過循環在每次迭代時將變量task
設置為不同的值。 第一個goroutine在task
設置為第二個值之前不會運行。 運行此示例以查看在每次迭代時將相同的地址傳遞給PrintData。
有幾種方法可以解決這個問題。 第一種是在切片中使用*Task
:
tasks := []*Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
go task.PrintData()
}
第二個是在循環內創建一個新變量:
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
task := task
go task.PrintData()
}
第三種是獲取切片元素的地址(使用自動插入的地址操作):
tasks := []Task{{"hello", 1}, {"world", 2}}
for i := range tasks {
go tasks[i].PrintData()
}
另一種選擇是將PrintData更改為值接收器,以防止方法調用自動獲取task
地址:
func (this Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
此問題類似於閉包和goroutines FAQ中討論的問題 。 問題之間的區別是用於將指針傳遞給goroutine函數的機制。 問題中的代碼使用方法的receiver參數。 FAQ中的代碼使用閉包 。
使用具有並發性的閉包時可能會出現一些混淆。 考慮以下程序:
func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
人們可能錯誤地期望看到a,b,c作為輸出。 您可能會看到的是c,c,c。 這是因為循環的每次迭代都使用變量v的相同實例,因此每個閉包共享該單個變量。 當閉包運行時,它會在執行fmt.Println時打印v的值,但是自goroutine啟動以來v可能已被修改。 為了幫助發現這個問題和其他問題,請運行go vet。
要在啟動時將v的當前值綁定到每個閉包,必須修改內部循環以在每次迭代時創建新變量。 一種方法是將變量作為參數傳遞給閉包:
for _, v := range values { go func(u string) { fmt.Println(u) done <- true }(v) }
在此示例中,v的值作為參數傳遞給匿名函數。 然后可以在函數內部訪問該值作為變量u。
更簡單的是創建一個新的變量,使用一個看似奇怪的聲明樣式但在Go中運行正常:
for _, v := range values { v := v // create a new 'v'. go func() { fmt.Println(v) done <- true }() }
只需使用聲明樣式為閉包創建一個新變量,該聲明樣式可能看似奇怪但在Go中工作正常。 添加task := task
。 例如,
package main
import (
"fmt"
"time"
)
type Task struct {
name string
data int32
}
func (this *Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
func main() {
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
task := task
go task.PrintData()
}
time.Sleep(time.Second * 5000)
}
輸出:
hello : 1
world : 2
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.