通道阻塞
细心的同学可能已经注意到,在介绍通道声明和初始化时,我们没有指定通道的容量:
package main
import "fmt"
func main() {
// 存储整数数据的通道
ch1 := make(chan int) // 以下同理
// 存储布尔数据的通道
ch2 := make(chan bool)
// 存储 []int 数据的通道
ch3 := make(chan []int)
fmt.Println(ch1, ch2, ch3)
}
但在演示通道操作时,我们指定了通道的容量:
package main
import "fmt"
func main() {
// 指定通道容量为 3
ch := make(chan int, 3)
ch <- 10
ch <- 20
fmt.Println(<-ch)
fmt.Println(<-ch)
}
对于没有指定容量的通道,它们被称为无缓冲通道(unbuffered channel),没有缓冲区空间。
如果发送方或接收方没有准备好,第一个操作将会阻塞,直到另一个操作准备就绪。
chan1 := make(chan int) // 无缓冲的 int 类型通道
对于指定了容量和缓冲区空间的通道,它们被称为缓冲通道(buffered channel)。
chan2 := make(chan int, 5) // 容量为 5 的 int 类型缓冲通道
它们的功能类似于队列,遵循先进先出(FIFO)规则。
对于缓冲通道,我们可以使用发送操作将元素追加到队列的末尾,并使用接收操作从队列的头部移除元素。
如果我们将超过容量的数据放入缓冲通道会发生什么?让我们来验证一下。将以下代码写入 channel1.go
文件:
cd ~/project
touch channel1.go
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 10
ch <- 20
fmt.Println("succeed")
}
使用以下命令运行程序:
go run channel1.go
程序输出如下:
fatal error: all goroutines are asleep - deadlock!
我们发现程序死锁了,因为通道已经满了。
为了解决这个问题,我们可以先从通道中提取数据。在 channel.go
中编写以下代码:
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 10
fmt.Println("提取的数据:", <-ch)
ch <- 20
fmt.Println("succeed")
}
使用以下命令运行程序:
go run channel.go
程序输出如下:
提取的数据: 10
succeed
通过采取取用、再取用的策略,我们可以持续使用容量有限的通道。