go-channel
go有个经典的话,不要通过共享内存来通信,而应该通过通信来共享数据。channel就是这个思想重要体现
channel是 Go 的内置类型, 可以理解为通道- 利用channel通道在协程之间通信
- 用于goroutine(协程并发任务)之间传数据
- channel也用于同步协调,在多个任务之间通信时候,同步和协调多个并发任务的执行
- 当任务接收channel数据时,如果还没有数据,当前goroutine 会阻塞等待
- 同步协调理解为:让多个并发任务在合适的时机等待,按照依赖关系配合起来
- channel理解为:任务之间通信 + 同步协调
channel定义
- chan int 是一个整体的类型,表示变量类型
- 表示可以传递 int 的 channel 类型
// 传递int类型数据的channel
var ch1 chan int
// 传递string类型数据channel
var ch2 chan string
// 使用make创建
ch := make(chan int)
channel 发送和接收数据
<-用于channel发送和接收数据的操作符。
// 把10发送到ch这个channel里
ch <- 10
// 从 ch 这个 channel 接收数据,并赋值给 num
num := <-ch
有缓冲 channel 和无缓冲 channel
无缓冲 channel 在发送和接收双方都准备好时才能完成通信,因此同步性强;有缓冲 channel 内部可以暂存一定数量的数据
make(chan T)不写容量,就是无缓冲make(chan T, n)的n是缓冲区容量
有缓冲
- 数据暂时放在缓冲区里,需要定义缓冲容量
- 数据即使还没人接收,也不会阻塞,不会造成死锁
- 当缓冲容量数据放满了,也会造成阻塞,因为缓冲满了,继续发送数据,也放不进去,发送方就会阻塞,直到有接收方收到数据,来消费这个数据才会腾出这个位置,后续阻塞的才会继续执行
- 接收缓冲区的数据,如果缓冲区空的,接收时,也会阻塞,等待有数据,才会继续执行
- 适合任务队列、并发限流场景
无缓冲
- 无缓冲 channel:具备同步作用
- 无缓冲不暂存数据,接收和发送数据需要同时同步进行
- 可以理解为无缓冲 channel的同步类似 打电话,需要双方都准备好
channel死锁
Go 的 channel 死锁通常发生在 goroutine 永久等待 channel 的发送或接收,而系统中没有其他 goroutine 能解除这个阻塞时
- 所有 goroutine 都在等,但没有任何一个 goroutine 能继续往下执行
channel的发送和接收没有匹配上问题- 比如常见的场景
- 定义了无缓冲 channel,发送了数据,但是没有接收,就会阻塞
- 包括无缓冲 channel 没有对应接收方、有缓冲 channel 写满后继续写、空 channel 继续读取、
range channel未关闭
channel无缓冲死锁问题
- 无缓冲 channel:发送时,必须同时有接收方准备接收,否则发送操作就会阻塞,导致死锁
- 有缓冲 channel:只要缓冲区还没满,发送就不会阻塞;缓冲区满了之后,继续发送才会阻塞
无缓冲 channel:没人接收就阻塞 有缓冲 channel:缓冲区满了才阻塞 有缓冲 channel 接收时:缓冲区空了就阻塞
range channel 未关闭理解
for v := range ch不知道什么时候该结束,它会一直等待 channel 后续是否还有数据- 发送方不再发数据时,应通过
close(ch)来关闭通道 range不是读到 channel 为空就结束,而是读到 channel 已关闭且数据已经全部消费完才结束
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch) // 关闭后不能再发送;还能继续接收剩余数据
for v := range ch {
fmt.Println(v)
}
}
注意问题
- goroutine任务之间通信问题
- 接收方如果先执行,而发送方还没准备好数据,接收方会阻塞等待
例子理解
并发任务例子
- 两个 goroutine 并发执行任务,然后通过 channel 把结果传回 main
- 注意这个例子中,任务1和任务2输出的顺序是不确定的,因为这2个任务都是并发执行,哪个任务谁先完成,就先把结果发送给channel
package main
import "fmt"
func main() {
ch := make(chan string)
// 并发任务 1
go func() {
ch <- "任务1完成"
}()
// 并发任务 2
go func() {
ch <- "任务2完成"
}()
// 接收两个并发任务返回的数据
msg1 := <-ch
msg2 := <-ch
fmt.Println(msg1)
fmt.Println(msg2)
}
任务之间传递数据
- 问题:假如任务2先执行,任务1还没执行完,那么任务2如何拿到任务1的数据?
channel重要的地方:接收方如果先执行,而发送方还没准备好数据,接收方会阻塞等待- 如果任务2先执行,那么任务2会先等待任务1,直到任务1执行完成,接收到数据
- 因为
channel不只是“传数据”,还会帮 goroutine 做同步协调
package main
import "fmt"
func main() {
ch := make(chan string)
// 任务1:发送数据
go func() {
ch <- "任务1产生的数据"
}()
// 任务2:接收任务1的数据
go func() {
msg := <-ch
fmt.Println("任务2收到:", msg)
}()
fmt.Scanln()
}