10.02-Channel

Channels

Channels (頻道) 提供一種方式,供兩個 goroutines 可以互相通信,並彼此同步執行。下列是一個使用 channels 的範例程式:

package main

import (
    "fmt"
    "time"
)

func pinger(c chan string) {
    for i := 0; ; i++ {
        c <- "ping"
    }
}
func printer(c chan string) {
    for {
        msg := <- c
        fmt.Println(msg)
        time.Sleep(time.Second * 1)
    }
}
func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}

此程式將會持續印出 "ping"(直到按下 Enter 為止)。一個 channel type (頻道型別) 的表示方式是使用關鍵字 chan 以及接著要傳遞給 channel 的資料之型別(在此例中,我們傳遞字串)。

而 <- (左箭頭)運算符則用於傳送與接收 channel 的訊息,c <- "ping" 表示送出 "ping",而 msg := <- c 表示接收一個訊息,並將訊息儲存於 msgfmt 這行也可以寫成:fmt.Println(<-c),在此例我們可以移除前面那行。

Using a channel like this synchronizes the two goroutines. When pinger attempts to send a message on the channel it will wait until printer is ready to receive the message. (this is known as blocking) Let's add another sender to the program and see what happens. Add this function:

func ponger(c chan string) {
    for i := 0; ; i++ {
        c <- "pong"
    }
}

And modify main:

func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go ponger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}

The program will now take turns printing “ping” and “pong”.

Channel Direction

We can specify a direction on a channel type thus restricting it to either sending or receiving. For example pinger's function signature can be changed to this:

func pinger(c chan<- string)

Now c can only be sent to. Attempting to receive from c will result in a compiler error. Similarly we can change printer to this:

func printer(c <-chan string)

A channel that doesn't have these restrictions is known as bi-directional. A bi-directional channel can be passed to a function that takes send-only or receive-only channels, but the reverse is not true.

Select

Go has a special statement called select which works like a switch but for channels:

func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        for {
            c1 <- "from 1"
            time.Sleep(time.Second * 2)
        }
    }()
    go func() {
        for {
            c2 <- "from 2"
            time.Sleep(time.Second * 3)
        }
    }()
    go func() {
        for {
            select {
            case msg1 := <- c1:
                fmt.Println(msg1)
            case msg2 := <- c2:
                fmt.Println(msg2)
            }
        }
    }()
    
    var input string
    fmt.Scanln(&input)
}

This program prints “from 1” every 2 seconds and “from 2” every 3 seconds. select picks the first channel that is ready and receives from it (or sends to it). If more than one of the channels are ready then it randomly picks which one to receive from. If none of the channels are ready, the statement blocks until one becomes available.

The select statement is often used to implement a timeout:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
}

time.After creates a channel and after the given duration will send the current time on it. (we weren't interested in the time so we didn't store it in a variable) We can also specify adefault case:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nothing ready")
}

The default case happens immediately if none of the channels are ready.

Buffered Channels

It's also possible to pass a second parameter to the make function when creating a channel:

c := make(chan int, 1)

This creates a buffered channel with a capacity of 1. Normally channels are synchronous; both sides of the channel will wait until the other side is ready. A buffered channel is asynchronous; sending or receiving a message will not wait unless the channel is already full.

Comments