Golang: Language: Concurrency: Unbuffered Channel

23rd December 2020 at 8:51am

Channels

Channel 类似 Unix Pipe,是两个 goroutine 之间通信的管道。Channel 带有一个类型,表示通信的数据只能是这种类型。定义一个 channel:

var a chan int

此时 a 的值是 nil,没有什么意义。一般用 make 来初始化 channel:

a:= make(chan int)

对 channel 有两种操作,都用的是箭头操作符(<-):

data := <- a // 从 channel a 中读出值并赋值给 data 
a <- data    // 将 data 写入 channel a

默认情况下,对 channel 的读写会阻塞;比如一个 goroutine 写入了数据,需要等另外一端读取了数据,才能继续运行;反之亦然:

package main

import (  
    "fmt"
)

func hello(done chan bool) {  
    fmt.Println("Hello world goroutine")
    done <- true
}
func main() {  
    done := make(chan bool)
    go hello(done)
    <-done
    fmt.Println("main function")
}

运行过程:

main                         | hello
====================================================================
go hello(done)               |
                             | fmt.Println("Hello world goroutine")
                             |
(wait for data from done)    | done <- true
<-done                       | (goroutine terminated)
fmt.Println("main function") |
(goroutine terminated)       |

死锁

使用 channel 时要注意死锁的情况。一旦向某一 channel 发送了数据,如果没有被读取程序就结束了;或者某一 goroutine 在等待写入 channel 的数据,没有等到程序就结束了,会引起 panic:

package main

func main() {  
    ch := make(chan int)

    ch <- 5
    // 换成接收 <- ch,也会造成死锁
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:  
main.main()  
    /tmp/sandbox249677995/main.go:6 +0x80

单向 channel

上面描述的都是双向 channel,可收可发。你也可以定义单向 channel:

  • 双向:chan int
  • 单向:
    • 只发:chan<- int
    • 只收:<-chan int

发跟收区别在于 chan 关键字变成 chan<- 还是 <-chan。只发是箭头指向 chan;只收是箭头从 chan 出来。

这样的代码过不了编译,因为从一个只发的 channel 中读取数据:

func receiveData(recvch chan<- int) {
    fmt.Println(<-recvch)
}
invalid operation: <-recvch (receive from send-only type chan<- int)

关闭 channel 以及使用 for range 读取 channel

关闭一个 channel:

close(ch)

判断一个 channel 是否已关闭:

v, ok := <-ch
// ok 为 true 时未关闭,为 false 时已关闭

可以用 for 循环消费 channel(但下文描述的 for-range 方式更佳):

for {
    v, ok := <-ch
    if ok == false {
        break
    }
    fmt.Println("Received ", v, ok)
}

for-range 方式:

for v := range ch {
    fmt.Println("Received ",v)
}