1. 程式人生 > 其它 >golang迴圈變數繫結

golang迴圈變數繫結

首先上程式碼

題目一:

package main
import (
    "fmt"
    "time"
)

func Process1(tasks []string) {
    for _, task := range tasks {
        // 啟動協程併發處理任務
        go func() {
            fmt.Printf("Worker start process task: %s\n", task)
        }()
    }
}


func main() {
    tasks := []string{"1", "2", "3", "
4", "5"} Process1(tasks) time.Sleep(2 * time.Second) } #連續執行兩次,輸出結構不一致: kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go Worker start process task: 5 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5 kun@kundeMacBook
-Pro study % go run goroutine/goroutine4.go Worker start process task: 4 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5

再執行下面程式碼

題目二:

package main
import (
    "fmt"
    "time"
)

func Process1(tasks []string) {
    for _, task := range tasks {
        
// 啟動協程併發處理任務 go func() { fmt.Printf("Worker start process task: %s\n", task) }() } } func Process2(tasks []string) { for _, task := range tasks { // 啟動協程併發處理任務 go func(t string) { fmt.Printf("Worker start process task: %s\n", t) }(task) } } func main() { tasks := []string{"1", "2", "3", "4", "5"} Process2(tasks) time.Sleep(2 * time.Second) } #除順序不一致,執行結果可以達到預期: kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go Worker start process task: 2 Worker start process task: 1 Worker start process task: 5 Worker start process task: 4 Worker start process task: 3 kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go Worker start process task: 1 Worker start process task: 3 Worker start process task: 4 Worker start process task: 5 Worker start process task: 2

題目三:

package main
import (
    "fmt"
    "time"
)

func Process1(tasks []string) {
    for _, task := range tasks {
        // 啟動協程併發處理任務
        go func() {
            fmt.Printf("Worker start process task: %s\n", task)
        }()
    }
}
func Process2(tasks []string) {
    for _, task := range tasks {
        // 啟動協程併發處理任務
        go func(t string) {
            fmt.Printf("Worker start process task: %s\n", t)
        }(task)
    }
}

func Process3(tasks []string){
    for i:=0;i<len(tasks);i++{
        j := i
        go func() {
            fmt.Printf("Worker start process task:%s\n", tasks[j])
        }()
    }
}

func main() {
    tasks := []string{"1", "2", "3", "4", "5"}
    Process3(tasks)
    time.Sleep(2 * time.Second)
}

#執行輸出如下,結果也能達到預期:
kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
Worker start process task:5
Worker start process task:2
Worker start process task:4
Worker start process task:1
Worker start process task:3
kun@kundeMacBook-Pro study % go run goroutine/goroutine4.go
Worker start process task:3
Worker start process task:1
Worker start process task:2
Worker start process task:5
Worker start process task:4

以下分析摘自網上資料:

原理剖析

上述問題,有個共同點就是都引用了迴圈變數。即在for index, value := range xxx語句中,
indexvalue便是迴圈變數。不同點是迴圈變數的使用方式,有的是直接在協程中引用(題目一),有的作為引數傳遞(題目二)。

回答以上問題,記住以下兩點即可。

迴圈變數是易變的

首先,迴圈變數實際上只是一個普通的變數。

語句for index, value := range xxx中,每次迴圈index和value都會被重新賦值(並非生成新的變數)。

如果迴圈體中會啟動協程(並且協程會使用迴圈變數),就需要格外注意了,因為很可能迴圈結束後協程才開始執行,
此時,所有協程使用的迴圈變數有可能已被改寫。(是否會改寫取決於引用迴圈變數的方式)

迴圈變數需要繫結

在題目一中,協程函式體中引用了迴圈變數task,協程從被建立到被排程執行期間迴圈變數極有可能被改寫,
這種情況下,我們稱之為變數沒有繫結。
所以,題目一列印結果是混亂的。很有可能(隨機)所有協程執行的task都是列表中的最後一個task。

在題目二中,協程函式體中並沒有直接引用迴圈變數task,而是使用的引數。而在建立協程時,迴圈變數task
作為函式引數傳遞給了協程。引數傳遞的過程實際上也生成了新的變數,也即間接完成了繫結。
所以,題目二實際上是沒有問題的。

在題目三中,通過 j:= i顯式地繫結,每次迴圈會生成一個新的變數

總結

簡單點來說

  • 如果迴圈體沒有併發出現,則引用迴圈變數一般不會出現問題;
  • 如果迴圈體有併發,則根據引用迴圈變數的位置不同而有所區別
    • 通過引數完成繫結,則一般沒有問題;
    • 函式體中引用,則需要顯式地繫結