簡體   English   中英

如何將方法用作goroutine函數

[英]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中的代碼使用閉包

Go常見問題(FAQ)

關閉作為goroutines運行的閉包會發生什么?

使用具有並發性的閉包時可能會出現一些混淆。 考慮以下程序:

 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.

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