Concurrency

Goroutines

go

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")
}

Channels

Channel = 管道,使用 <- 來將值傳入管道或由管道中接收值。

ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and
           // assign value to v.

跟 maps 與 slices 相同,需要使用 make 來宣告:

ch := make(chan int)

channel 必須指定型別

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

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

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

Buffered Channels

Channel 是可以有緩衝的。

ch := make(chan int, 100)

當 channel 滿的時候將值傳入 channel 將會被阻塞,而當 channel 為空時接收值也會被阻塞。

package main

import (
    "fmt"
    "time"
)

func put(x int, c chan int) {
    c <- x
}

func putLater(x int, c chan int) {
    time.Sleep(2 * time.Second)
    put(x, c)
}

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    go put(3, ch)
    go putLater(4, ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Range and Close

若 channel 已經沒有值需要傳入時可以透過 close 來關閉 channel,也可以用接收時的第二個回傳值來判斷 channel 是否已經關閉

v, ok := <-ch

okfalse 時,代表沒有值可以接收而且 channel 已經被關閉。

使用 for i := range c 可以一直從 channel 接收值,直到 channel 被關閉。

註1: 只有發送者應該去關閉 channel,不要由接收方來關閉。傳入值到一個已經關閉的 channel 會導致 panic。

註2: Channel 不像檔案需要再使用後關閉,channel 通常不需要被關閉,只有需要告訴接收方已經沒有值需要接收時才需要關閉,像是結束一個 range 迴圈。

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 10)
    c <- 1
    close(c)
    value, ok := <-c
    fmt.Println(value, ok)
    value, ok = <-c
    fmt.Println(value, ok)

    channelForRange := make(chan int, 10)
    channelForRange <- 1
    channelForRange <- 2
    close(channelForRange)
    for i := range channelForRange {
        fmt.Println(i)
    }
}

Select

Select 可以用來等待多個 channel 的回傳。

Select 會阻塞住,直到任一個 case 可以執行。當多個 case 都可執行時它會隨機選擇一個 case 執行。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

Default Selection

switch 一樣,若有 defaultselect 將在沒有任何 case 可執行時來執行 default

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}