什么是通道?
了解 Golang 中的通信机制。
概述
如果 goroutine 是独立执行的,那么问题就出现了:goroutine 之间如何相互通信?答案是,在 Go 语言中,我们使用双向或单向通道(channel)在 goroutine 之间进行通信。
通道是一种在并发运行的函数之间通过发送和接收特定元素类型数据来实现通信的技术。通道可以是任意类型的,例如整型通道、字符串通道等。换句话说,当多个 goroutine 同时运行时,通道是它们之间通信最便捷的方式。简单来说,通道就是连接并发 goroutine 的管道。通道本质上是指针,因此我们可以轻松地将其传递给 goroutine,并方便地写入或读取数据。
通道是线程安全的,因此我们可以通过多个 goroutine 并发地使用通道变量来发送和接收值。
通道声明语法
创建通道的语法为 make(chan 值类型),其中值类型可以是字符串、整数、结构体、通道等。
若要创建一个整型通道,语法为 make(chan int)。该语句同时完成声明和内存分配。
1goChannel := make(chan int)
我们再声明另一个不同类型值的通道。假设要创建一个元素类型为 int 通道的通道,按照标准语法如下所示:
1goChannel := make(chan chan int)
现在,在代码中使用已声明的通道,示例如下:
1func main() { 2 goChannel := make(chan int) 3 fmt.Printf("通道的类型是 %T", goChannel) 4}
也可以使用以下语法声明通道:
1var goChannel chan int
上述语法会使通道的默认值为 nil。需使用 make 函数为其分配内存并赋值回变量。make 函数会返回指向已分配内存的引用,如下所示:
1func main() { 2 var goChannel chan int 3 goChannel = make(chan int) 4 fmt.Printf("通道的类型是 %T", goChannel) 5}
在 Go 语言中,如果我们需要重新赋值,不需要使用冒号。
通过通道发送或接收数据
初始化通道后,就可以通过它发送或接收值。以下是发送和接收值的语法:
-
发送值:
1goChannel <- value 2// value 必须是声明时指定的类型 -
接收值:
1<-goChannel
将初始化、发送和接收操作合并到一段代码中,程序如下所示:
1package main 2 3import "fmt" 4 5func main() { 6 goChannel := make(chan int) 7 goChannel <- 1 8 fmt.Println("Value ", <-goChannel) 9}
可以看到,运行这段代码时会出现错误。为什么?虽然通道已声明,内存已分配,也进行了值的发送和接收,但仍然报错。
原因是通道的作用是在 goroutine 之间通信并实现同步。通道只有在发送方和接收方都准备就绪时才能正常工作。而上述代码是顺序执行的:先发送值,但此时没有接收方,程序会一直等待,直到有对应的 goroutine 准备好接收数据,这就导致了死锁错误。
为了成功执行代码,应将发送值的操作放在一个 goroutine 中。
1package main 2 3import "fmt" 4 5func main() { 6 var goChannel chan int 7 goChannel = make(chan int) 8 go func() { 9 goChannel <- 1 10 }() 11 fmt.Println("通道内的值为 ", <-goChannel) 12}
我们逐步分析这段代码:由于我们在代码中启动了一个 goroutine,其中的代码独立执行。第 6、7 和 11 行由主 goroutine 顺序执行,而第 8 到 10 行则与主 goroutine 并发执行。第 11 行会等待通道中的值,而并发运行的 goroutine 正好负责发送该值。由于发送方和接收方同时就绪,程序得以无错误地运行。
关闭通道
要关闭通道,只需使用内置的 close() 函数。一旦通道被关闭,就无法再向其发送任何值。
内置函数如下:
1close()
在代码中使用方式如下:
1func channel(goChan chan int) { 2 for v := 0; v < 2; v++ { 3 goChan <- v 4 } 5 close(goChan) 6}
只需将通道名称作为参数传入 close 函数即可。
接收操作的返回值
从通道接收数据时,会返回两个值:一个是通过通道发送的实际值,另一个是布尔标志,用于指示该值是正常接收到的(来自写入操作),还是因为通道已被关闭而返回的零值。
执行以下代码示例:
1package main 2import "fmt" 3 4func channel(goChan chan int) { 5 goChan <- 1 6 close(goChan) 7} 8 9func main() { 10 goChannel := make(chan int, 5) 11 go channel(goChannel) 12 value, ok := <-goChannel 13 fmt.Println(value) 14 fmt.Println(ok) 15 value, ok = <-goChannel 16 fmt.Println(value) 17 fmt.Println(ok) 18}
当值由写入操作发送时,布尔标志为 true;当通道被关闭后接收值时,标志为 false。这个布尔标志帮助我们判断通道当前是打开状态还是已关闭状态。