## Channel
那么如果启动了多个 goroutine,它们之间该如何通信呢?这就是 Go 语言提供的 channel(通道)要解决的问题。
**创建一个 channel**
```
ch := make(chan 数据类型)
ch1 := make(chan int) // 创建一个整型类型的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip
```
**发送数据**
通道的发送使用特殊的操作符 `<-`,将数据通过通道发送的格式为:
通道变量 <- 值
使用 `make` 创建一个通道后,就可以使用`<-`向通道发送数据,代码如下:
```
// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"
```
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。
**接收数据**
通道接收同样使用`<-`操作符
通道的数据接收一共有以下写法。
1. 阻塞接收数据
阻塞模式接收数据时,将接收变量作为`<-`操作符的左值,格式如下:
```
data := <-ch
```
执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
2. 忽略接收的数据
阻塞接收数据后,忽略从通道返回的数据,格式如下:
```
<-ch
```
执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。
3. 循环接收
通道的数据接收可以借用 for range 语句进行多个元素的接收操作,格式如下:
```
for data := range ch {
}
```
通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过 for 遍历获得的变量只有一个,即上面例子中的 data。
```
package main
import (
"fmt"
"time"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
// 从3循环到0
for i := 3; i >= 0; i-- {
// 发送3到0之间的数值
ch <- i
// 每次发送完时等待
time.Sleep(time.Second)
}
}()
// 遍历接收通道数据
for data := range ch {
// 打印通道数据
fmt.Println(data)
// 当遇到数据0时, 退出接收循环
if data == 0 {
break
}
}
}
```
### 无缓冲 channel
上面的示例中,使用 make 创建的 chan 就是一个无缓冲 channel,它的容量是 0,不能存储任何数据。所以无缓冲 channel 只起到传输数据的作用,数据并不会在 channel 中做任何停留。这也意味着,无缓冲 channel 的发送和接收操作是同时进行的,它也可以称为同步 channel。
### 有缓冲 channel
有缓冲 channel 类似一个可阻塞的队列,内部的元素先进先出。通过 make 函数的第二个参数可以指定 channel 容量的大小,进而创建一个有缓冲 channel,如下面的代码所示:
```
cacheCh:=make(chan int,5)
```
一个有缓冲 channel 具备以下特点:
1. 有缓冲 channel 的内部有一个缓冲队列;
2. 发送操作是向队列的尾部插入元素,如果队列已满,则阻塞等待,直到另一个 goroutine 执行,接收操作释放队列的空间;
3. 接收操作是从队列的头部获取元素并把它从队列中删除,如果队列为空,则阻塞等待,直到另一个 goroutine 执行,发送操作插入新的元素。
因为有缓冲 channel 类似一个队列,可以获取它的容量和里面元素的个数。如下面的代码所示:
```
cacheCh:=make(chan int,5)
cacheCh <- 2
cacheCh <- 3
fmt.Println("cacheCh容量为:",cap(cacheCh),",元素个数为:",len(cacheCh))
```
通过内置函数 cap 可以获取 channel 的容量,也就是最大能存放多少个元素,通过内置函数 len 可以获取 channel 中元素的个数。
### 单向 channel
有时候,我们有一些特殊的业务需求,比如限制一个 channel 只可以接收但是不能发送,或者限制一个 channel 只能发送但不能接收,这种 channel 称为单向 channel。
单向 channel 的声明也很简单,只需要在声明的时候带上 <- 操作符即可,如下面的代码所示:
```
// 接收通道
ch := make(chan<- int)
// 发送通道
ch := make(<-chan int)
```
### 关闭 channel
channel 还可以使用内置函数 `close`关闭。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`获取channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。