Go中的Channel
Channel的概念
Channel是Go中的一个核心概念,它是一种类型,一种引用类型。声明一个Channel类型的变量时,需要指定它的元素类型。同时,Channel提供了两种操作:发送(Send)和接收(Receive)。发送和接收都使用<-操作符。
ch <- v // 将v发送到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
和Map与Slice一样,Channel使用前必须创建:
ch := make(chan int)
Channel的零值是nil。
Channel可以用于两个Goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<-用于指定Channel的方向,发送或接收。如果未指定方向,则为双向Channel。
ch := make(chan int) // 双向Channel
ch := make(chan int, 0) // 无缓冲Channel,同双向Channel
ch := make(chan int, 3) // 缓冲Channel,容量为3
无缓冲Channel又称为同步Channel,发送(Send)操作会被阻塞,直到另一个Goroutine在相同的Channel上执行接收(Receive)操作,这时值才能发送成功,两个操作在相同的时间才能完成。反之,如果接收操作先执行,接收方的Goroutine会被阻塞,直到另一个Goroutine在相同的Channel上发送一个值。
ch := make(chan int)
go func() {
x := <-ch
fmt.Println("Received:", x)
}()
fmt.Println("Sending:", 10)
ch <- 10
fmt.Println("Sent:", 10)
上面的代码会输出:
Sending: 10
Received: 10
Sent: 10
如果将上面代码中的无缓冲Channel改为缓冲Channel,那么输出结果会是什么呢?
ch := make(chan int, 1)
go func() {
x := <-ch
fmt.Println("Received:", x)
}()
fmt.Println("Sending:", 10)
ch <- 10
fmt.Println("Sent:", 10)
输出结果:
Sending: 10
Sent: 10
Received: 10
因为缓冲Channel的容量为1,所以发送操作不会被阻塞,直到接收操作执行完毕,两个操作才算完成。
Channel的关闭
Channel可以被显式地关闭以表示不再向Channel发送任何值。关闭Channel后,任何发送操作都会导致panic异常。
ch := make(chan int)
close(ch)
ch <- 10 // panic: send on closed channel
接收操作可以检测Channel是否被关闭,如果Channel被关闭了,那么接收操作会立即完成,同时获取到Channel返回的零值。
ch := make(chan int)
close(ch)
x, ok := <-ch
fmt.Println(x, ok) // 0 true
如果Channel的元素类型是bool类型,那么获取到的零值为false。
ch := make(chan bool)
close(ch)
fmt.Println(<-ch) // false
如果Channel的元素类型是引用类型,那么获取到的零值为nil。
ch := make(chan []int)
close(ch)
fmt.Println(<-ch) // []
如果Channel的元素类型是接口类型,那么获取到的零值为nil。
ch := make(chan interface{})
close(ch)
fmt.Println(<-ch) // <nil>
Channel的遍历
Channel可以使用for range的方式进行遍历,就像遍历Slice或Map一样。
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
输出结果:
0
1
2
Channel的选择
Channel可以使用select语句进行选择,就像使用switch语句一样。
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for {
select {
case v := <-ch1:
fmt.Println("Received:", v)
case v := <-ch2:
fmt.Println("Received:", v)
}
}
}()
for i := 0; i < 10; i++ {
select {
case ch1 <- 10:
case ch2 <- 20:
}
}
输出结果:
Received: 10
Received: 20
Received: 10
Received: 20
Received: 10
Received: 20
Received: 10
Received: 20
Received: 10
Received: 20
Channel的应用
1. 用于goroutine之间的同步
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
ch := make(chan struct{})
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i)
<-ch
wg.Done()
}(i)
}
close(ch)
wg.Wait()
}
输出结果:
0
1
2
3
4
5
6
7
8
9
2. 用于goroutine之间的通讯
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
ch := make(chan int)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i)
ch <- i
wg.Done()
}(i)
}
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
}()
wg.Wait()
}
输出结果:
0
1
2
3
4
5
6
7
8
9
3. 用于goroutine之间的取消
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
ch := make(chan struct{})
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i)
<-ch
wg.Done()
}(i)
}
for i := 0; i < 10; i++ {
ch <- struct{}{}
}
wg.Wait()
}
输出结果:
0
1
2
3
4
5
6
7
8
9