GO语言的Channel | 青训营笔记

53 阅读1分钟

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