Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。
Channel类型
和 map 类似,channel 也一个对应 make 创建的底层数据结构的引用。当我们复制一个 channel 或用于函数参数传递时,我们只是拷贝了一个 channel 引用,因此调用者和被调用者将引用同一个 channel 对象。和其它的引用类型一样,channel 的零值也是 nil。
Channel类型的定义格式如下
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
它包括三种类型的定义。可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
chan T // 可以接收和发送类型为 T 的数据
chan<- float64 // 只可以用来发送 float64 类型的数据
<-chan int // 只可以用来接收 int 类型的数据
定义一个 channel 时,也需要定义发送到 channel 的值的类型。channel 可以使用内置的 make () 函数来创建:
chan 是创建 channel 所需使用的关键字。Type 代表指定 channel 收发数据的类型。
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity)
无缓冲的 channel
无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何数据值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
无缓冲的 channel 创建容量为0的就可以了
make(chan Type)
make(chan Type, 0)
如:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 0) //创建无缓冲的通道 c
go func() {
defer fmt.Println("子协程结束")
for i := 0; i < 3; i++ {
fmt.Println("子协程发送数据: ", i)
c <- i
}
}()
time.Sleep(2 * time.Second) //延时2s
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("main接收数据: ", num)
}
fmt.Println("main结束")
// 延迟退出,等待子协程结束
time.Sleep(2 * time.Second)
}
输出结果:
子协程发送数据: 0
main接收数据: 0
子协程发送数据: 1
子协程发送数据: 2
main接收数据: 1
main接收数据: 2
main结束
子协程结束
有缓冲的 channel
有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个数据值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。
- 只有通道中没有要接收的值时,接收动作才会阻塞。
- 只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
有缓冲的 channel 创建需要指定容量大小,写入数据超出大小时会写阻塞
make(chan Type, capacity)
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) //创建无缓冲的通道 c
go func() {
defer fmt.Println("子协程结束")
for i := 0; i < 3; i++ {
fmt.Println("子协程发送数据: ", i)
c <- i
}
}()
time.Sleep(2 * time.Second) //延时2s
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("main接收数据: ", num)
}
fmt.Println("main结束")
// 延迟退出,等待子协程结束
time.Sleep(2 * time.Second)
}
输出结果:
子协程发送数据: 0
子协程发送数据: 1
子协程发送数据: 2
子协程结束
main接收数据: 0
main接收数据: 1
main接收数据: 2
main结束
关闭 channel
如果发送者知道,没有更多的值需要发送到 channel 的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的 close 函数来关闭 channel 实现。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) //创建无缓冲的通道 c
go func() {
defer fmt.Println("子协程结束")
for i := 0; i < 3; i++ {
fmt.Println("子协程发送数据: ", i)
c <- i
}
close(c)
}()
time.Sleep(2 * time.Second) //延时2s
for num := range c {
fmt.Println("main接收数据: ", num)
}
fmt.Println("main结束")
// 延迟退出,等待子协程结束
time.Sleep(2 * time.Second)
}
输出结果
子协程发送数据: 0
子协程发送数据: 1
子协程发送数据: 2
子协程结束
main接收数据: 0
main接收数据: 1
main接收数据: 2
main结束
- channel 不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束 range 循环之类的,才去关闭 channel;
- 关闭 channel 后,无法向 channel 再发送数据 (引发 panic 错误后导致接收立即返回零值);
- 关闭 channel 后,可以继续从 channel 接收数据;
- 对于 nil channel,无论收发都会被阻塞。
本文参考:https://learnku.com/docs/bettercoding/1.0/channel-basic-use/6828