Go 語言編程 — 併發

目錄

goroutine

Golang 原生支持併發,體現在 Golang 提供了 go 關鍵字。

格式:

go 函數名(形參列表)

go 語句會直接開啓一個新的運行期線程,即:goroutine。以一個不同的、新創建的 goroutine 來執行一個函數。goroutine 的本質是一個輕量級線程,goroutine 的調度由 Golang 運行時進行管理, 同一個程序中的所有 goroutine 共享同一個地址空間。

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

channel

channel(通道)是用來傳遞數據的一個數據結構。

通道可用於兩個 goroutine 之間通過傳遞一個指定類型的數值,以此來同步運行及通訊。操作符 <- 用於指定通道的方向,根據位置的不同表示發送或接收。如果未指定方向,則爲雙向通道。

ch <- v    // 把 v 發送到通道 ch
v := <-ch  // 從 ch 接收數據並把值賦給 v

使用 chan 關鍵字來定義一個通道變量:

ch := make(chan int)

需要注意的是,默認情況下,通道是不自帶緩衝區的。發送端發送數據,就必須同時存在接收端接收相應的數據。

以下示例通過兩個 goroutine 來計算數字之和,在 goroutine 完成計算後,它會計算兩個結果的和:

package main

import "fmt"

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c<- sum    // 把 sum 發送到通道 c
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    
    go sum(s[:(len(s) / 2)], c)
    go sum(s[(len(s) / 2):], c)
    x, y := <-c, <-c    // 從通道 c 中接收
    
    fmt.Println(x, y, x+y)
}

通道緩衝區

通道可以顯式設置緩衝區,通過 make 的第二個參數指定緩衝區大小:

ch := make(chan int, 100)

帶緩衝區的通道允許發送端的數據發送,和接收端的數據接收處於異步狀態,就是說發送端發送的數據可以放在緩衝區裏面,然後等待接收端去獲取數據,而不是要求接收端立刻去獲取數據。

需要注意的是,緩衝區的大小是有限的,所以還是必須要有接收端來接收數據,否則緩衝區一滿,數據發送端就無法再發送數據了。

如果通道不帶緩衝區,發送端會阻塞直到接收端從通道中接收了值。如果通道帶緩衝,發送端則會阻塞直到發送的值被拷貝到緩衝區內;如果緩衝區已滿,則意味着需要等待直到某個接收端獲取到一個值。接收端在有值可以接收之前會一直阻塞。

package main

import "fmt"

func main() {
    // 這裏我們定義了一個可以存儲整數類型的帶緩衝通道,緩衝區大小爲 2。
    ch := make(chan int, 2)

    // 因爲 ch 是帶緩衝的通道,我們可以同時發送兩個數據,而不用立刻需要去同步讀取數據。
    ch <- 1
    ch <- 2

    // 獲取這兩個數據
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

遍歷通道與關閉通道

通過 range 關鍵字還可以用於遍歷通道數據類型變量,實現遍歷讀取到的數據。如果通道接收不到數據,ok 變量則爲 false,這時通道就可以使用 close() 函數來關閉一個通道。

package main

import "fmt"

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)

    /**
     * range 函數遍歷每個從通道接收到的數據,
     * 因爲 c 在發送完 10 個數據之後就關閉了通道,
     * 所以這裏我們 range 函數在接收到 10 個數據之後就結束了。
     * 如果上面的 c 通道不關閉,那麼 range 函數就不會結束,從而在接收第 11 個數據的時候就阻塞了。
     */
    for i := range c {
        fmt.Println(i)
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章