Go信道

Posted by Saylst on April 10, 2018

Go信道

什么是信道

信道可以想象成Go协程之间通信的管道。通过使用信道,一端发送数据,一端则接受数据。

信道的声明

最简单的方式:

c := make(chan T)

其中,T为数据类型,信道只允许同种类型的数据传输。

信道收发数据

  • 写数据
    c <- data
    
  • 读数据
    data = <- c
    

信道旁的箭头方向指定了是发送数据还是接收数据。

在写数据时,箭头指向了 c,因此我们在把数据写入信道 c。

在读数据时,箭头对于 c 来说是向外指的,因此我们读取了信道 c 的值,并把该值存储到变量 data。

发送与接受数据默认是阻塞的。当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,知道有其它Go协程从信道读取数据,才会接触阻塞。与此类似,当读取信道数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。

信道的这种特性能够帮助Go协程之间进行高效的通信,不需要用到其他编程语言常见的显示锁或者条件变量。

代码示例:

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

在上述程序里,我们在第 12 行创建了一个 bool 类型的信道 done,并把 done 作为参数传递给了 hello 协程。在第 14 行,我们通过信道 done 接收数据。这一行代码发生了阻塞,除非有协程向 done 写入数据,否则程序不会跳到下一行代码。于是,这就不需要用以前的 time.Sleep 来阻止 Go 主协程退出了。

<- done 为接受数据。这样Go主协程就发生了阻塞,等待信道done发送的数据。该信道作为参数传递给了协程hello,hello打印出 Hello world goroutine,接下来向 done 写入数据。当完成写入时,Go 主协程会通过信道 done 接收数据,于是它解除阻塞状态,打印出文本 main function。

死锁

使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。

同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。

所以说,接受有一个协程往信道写数据,必然需要一个协程从信道中读数据,两者是成对出现的。

单向信道和信道转换

信道还可以定义成,只有发送或者只有接受功能的单向信道。例如:

sendch := make(chan<- int)

关闭信道和使用for range遍历信道

信道关闭使用close(chan)。并且可以通过

v, ok := <- ch

判断信道是否关闭,ok为false时,表示信道已关闭。代码示例:

package main

import (  
    "fmt"
)

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}
func main() {  
    ch := make(chan int)
    go producer(ch)
    for {
        v, ok := <-ch
        if ok == false {
            break
        }
        fmt.Println("Received ", v, ok)
    }
}

在上述的程序中,producer 协程会从 0 到 9 写入信道 chn1,然后关闭该信道。主函数有一个无限的 for 循环(第 16 行),使用变量 ok(第 18 行)检查信道是否已经关闭。如果 ok 等于 false,说明信道已经关闭,于是退出 for 循环。如果 ok 等于 true,会打印出接收到的值和 ok 的值。

本文转自:https://studygolang.com/articles/12343