在 Golang 中使用 Go 關鍵字和 Channel 實現並行

Airy發表於2019-02-16

Go 關鍵字和 channel 的用法

go 關鍵字用來建立 goroutine (協程),是實現併發的關鍵。go 關鍵字的用法如下:
//go 關鍵字放在方法呼叫前新建一個 goroutine 並讓他執行方法體
go GetThingDone(param1, param2);

//上例的變種,新建一個匿名方法並執行
go func(param1, param2) {
}(val1, val2)

//直接新建一個 goroutine 並在 goroutine 中執行程式碼塊
go {
    //do someting...
}
因為 goroutine 在多核 cpu 環境下是並行的。如果程式碼塊在多個 goroutine 中執行,我們就實現了程式碼並行。那麼問題來了,怎麼拿到並行的結果呢?這就得用 channel 了。
//resultChan 是一個 int 型別的 channel。類似一個信封,裡面放的是 int 型別的值。
var resultChan chan int
//將 123 放到這個信封裡面,供別人從信封中取用
resultChan <- 123
//從 resultChan 中取值。這個時候 result := 123
result := <- resultChan

使用 go 關鍵字和 channel 實現非阻塞呼叫

阻塞的意思是呼叫方在被呼叫的程式碼返回之前必須一直等待,不能處理別的事情。而非阻塞呼叫則不用等待,呼叫之後立刻返回。那麼返回值如何獲取呢?Node.js 使用的是回撥的方式,Golang 使用的是 channel。
/**
 * 每次呼叫方法會新建一個 channel : resultChan,
 * 同時新建一個 goroutine 來發起 http 請求並獲取結果。
 * 獲取到結果之後 goroutine 會將結果寫入到 resultChan。
 */
func UnblockGet(requestUrl string) chan string {
    resultChan := make(chan string)
    go func() {
        request := httplib.Get(requestUrl)
        content, err := request.String()
        if err != nil {
            content = "" + err.Error()
        }
        resultChan <- content
    } ()
    return resultChan
}

由於新建的 goroutine 不會阻塞函式主流程的執行,所以呼叫 UnblockGet 方法會立刻得到一個 resultChan 返回值。一旦 goroutine 執行完畢拿到結果就會寫入到 resultChan 中,這時外部就可以從 resultChan 中獲取執行結果。

一個很 low 的並行示例

fmt.Println(time.Now())
resultChan1 := UnblockGet("http://127.0.0.1/test.php?i=1")
resultChan2 := UnblockGet("http://127.0.0.1/test.php?i=2")

fmt.Println(<-resultChan1)
fmt.Println(<-resultChan1)
fmt.Println(time.Now())

上面兩個 http 請求是在兩個 goroutine 中並行的。總的執行時間小於 兩個請求時間和。

這個例子只是為了體現 go 和 channel 的用法,有記憶體洩漏問題,千萬不要線上上這麼搞。因為新建的 channel 沒有 close。下次寫一個更高階一點的。

簡單的實現 http multi GET

type RemoteResult struct {
    Url string
    Result string
}

func RemoteGet(requestUrl string, resultChan chan RemoteResult)  {
    request := httplib.NewBeegoRequest(requestUrl, "GET")
    request.SetTimeout(2 * time.Second, 5 * time.Second)
    //request.String()
    content, err := request.String()
    if err != nil {
        content = "" + err.Error()
    }
    resultChan <- RemoteResult{Url:requestUrl, Result:content}
}
func MultiGet(urls []string) []RemoteResult {
    fmt.Println(time.Now())
    resultChan := make(chan RemoteResult, len(urls))
    defer close(resultChan)
    var result []RemoteResult
    //fmt.Println(result)
    for _, url := range urls {
        go RemoteGet(url, resultChan)
    }
    for i:= 0; i < len(urls); i++ {
        res := <-resultChan
        result = append(result, res)
    }
    fmt.Println(time.Now())
    return result
}

func main() {
    urls := []string{
        "http://127.0.0.1/test.php?i=13",
        "http://127.0.0.1/test.php?i=14",
        "http://127.0.0.1/test.php?i=15",
        "http://127.0.0.1/test.php?i=16",
        "http://127.0.0.1/test.php?i=17",
        "http://127.0.0.1/test.php?i=18",
        "http://127.0.0.1/test.php?i=19",
        "http://127.0.0.1/test.php?i=20"    }
    content := MultiGet(urls)
    fmt.Println(content)
}

相關文章