16.9 閉包和協程的使用

請看下面代碼:

package main

import (
    "fmt"
    "time"
)

var values = [5]int{10, 11, 12, 13, 14}

func main() {
    // 版本A:
    for ix := range values { // ix是索引值
        func() {
            fmt.Print(ix, " ")
        }() // 調用閉包打印每個索引值
    }
    fmt.Println()
    // 版本B: 和A版本類似,但是通過調用閉包作爲一個協程
    for ix := range values {
        go func() {
            fmt.Print(ix, " ")
        }()
    }
    fmt.Println()
    time.Sleep(5e9)
    // 版本C: 正確的處理方式
    for ix := range values {
        go func(ix interface{}) {
            fmt.Print(ix, " ")
        }(ix)
    }
    fmt.Println()
    time.Sleep(5e9)
    // 版本D: 輸出值:
    for ix := range values {
        val := values[ix]
        go func() {
            fmt.Print(val, " ")
        }()
    }
    time.Sleep(1e9)
}

/* 輸出:

        0 1 2 3 4

        4 4 4 4 4

        1 0 3 4 2

        10 11 12 13 14

*/

版本A調用閉包5次打印每個索引值,版本B也做相同的事,但是通過協程調用每個閉包。按理說這將執行得更快,因爲閉包是併發執行的。如果我們阻塞足夠多的時間,讓所有協程執行完畢,版本B的輸出是:4 4 4 4 4。爲什麼會這樣?在版本B的循環中,ix變量 實際是一個單變量,表示每個數組元素的索引值。因爲這些閉包都只綁定到一個變量,這是一個比較好的方式,當你運行這段代碼時,你將看見每次循環都打印最後一個索引值4,而不是每個元素的索引值。因爲協程可能在循環結束後還沒有開始執行,而此時ix值是4

版本C的循環寫法纔是正確的:調用每個閉包是將ix作爲參數傳遞給閉包。ix在每次循環時都被重新賦值,並將每個協程的ix放置在棧中,所以當協程最終被執行時,每個索引值對協程都是可用的。注意這裏的輸出可能是0 2 1 3 4或者0 3 1 2 4或者其他類似的序列,這主要取決於每個協程何時開始被執行。

在版本D中,我們輸出這個數組的值,爲什麼版本B不能而版本D可以呢?

因爲版本D中的變量聲明是在循環體內部,所以在每次循環時,這些變量相互之間是不共享的,所以這些變量可以單獨的被每個閉包使用。

鏈接

results matching ""

    No results matching ""