通道(channel)
- 1. 声明
- 2. channel的操作
- 3. 无缓冲通道
- 4. 有缓冲通道
- 5. 如何优雅的从通道循环取值
- 6. 单向通道
- 7. 异常总结
上一篇:结构体
Go语言的并发模式:不要通过共享内存来通信,而应该通过通信来共享内存。
Go语言的通道(channel)是一种特殊类型,遵循先进先出(FIFO)的原则,声明channel时需要指定元素类型。
channel是一种通信机制,用于在不同的goroutine之间传递数据。
1. 声明
channel 是一种引用类型,空值是 nil
。声明后的 channel 变量是未初始化的,需要使用make函数初始化。
- 声明channel:
var 变量 chan 元素类型
- 创建channel:
ch := make(chan 元素类型, 缓冲大小)
元素类型:通道中存放的数据类型。
package main
import "fmt"
func main() {
// 声明
var ch chan int
fmt.Println(ch) // <nil>
// 初始化
ch = make(chan int, 3)
}
2. channel的操作
- 操作符:
<-
- 发送(send):
ch <- 元素值
- 接收(receive):
元素值 := <-ch
- 关闭(close):
close(ch)
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1 // 把 1 发送到 ch
ch <- 2
ch <- 3
//ch <- 4 // 超出缓冲大小:fatal error: all goroutines are asleep - deadlock!
i := <-ch // 从 ch 接收一个值,并赋值给 i
fmt.Println(i)
<-ch // 从 ch 接收一个值,并丢弃
<-ch
j, ok := <-ch // 通道未关闭且无元素值,则堵塞:fatal error: all goroutines are asleep - deadlock!
fmt.Printf("i = %d, j = %d, ok = %v\n", i, j, ok)
ch <- 4
close(ch) // 关闭通道 ch
//ch <- 5 // 通道关闭后,不可再发送:panic: send on closed channel
k, ok := <-ch // 通道关闭后,若通道内还有值,接收依然可以接收通道中的元素。
fmt.Printf("k = %d, ok = %v\n", k, ok)
m, ok := <-ch // 通道关闭且无元值,得到对应类型的零值。ok为false说明通道已没有元素值可取了
fmt.Printf("k = %d, ok = %v\n", m, ok)
}
关闭后的通道具有以下特点:
-
通道关闭后,发送操作引发
panic
。 -
通道关闭后,若通道内还有值,接收操作依然可以接收通道中的元素。
-
通道关闭且没有值,接收操作会得到对应类型的零值。接收表达式可接收两个值,第一个值为接收到的元素值,第二个值为bool类型,如果为false,则说明通道已没有元素值可取了,
且通道已关闭。
注:若通道未关闭且无元素值,接收操作堵塞。
-
关闭一个已关闭的通道,会引发panic。
3. 无缓冲通道
又称为堵塞通道。无论send还是receive一开始执行就会被堵塞,直到配对的操作也开始执行,才会继续传递。
同步方式传递数据:使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
package main
import "fmt"
func main() {
ch := make(chan int)
go func(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}(ch)
ch <- 10
fmt.Println("发送成功")
}
4. 有缓冲通道
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。
获取通道内元素数量:len(ch)
获取通道的容量:cap(ch)
func main() {
ch := make(chan int, 10) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}
异步方式传递数据
5. 如何优雅的从通道循环取值
通过channel发送有限数据时,可以通过close函数关闭通道来告诉通道接收值的goroutine停止等待。
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// goroutine
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
go func() {
for {
i, ok := <-ch1
if !ok { // ok为false,表面通道已关闭且没有元素值可取了
break
}
ch2 <- i * i
}
close(ch2)
}()
// 主goroutine
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}
6. 单向通道
有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。
Go语言中提供了单向通道来处理这种情况。例如,我们把上面的例子改造如下:
package main
import "fmt"
func counter(out chan<- int) { // 限制out仅为发送通道
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
func printer(in <-chan int) { // in限制为接收通道
for i := range in {
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1) // GO语言会自动把双向通道转换为函数所需要的单向通道
go squarer(ch2, ch1)
printer(ch2)
}