一 channel简介
如果在程序中开启了多个goroutine,那么这些goroutine之间该如何通信呢?
go提供了一个channel(管道)数据类型,可以解决协程之间的通信问题!channel的本质是一个队列,遵循先进先出规则(FIFO),内部实现了同步,确保了并发安全!
二 channel的创建
2.0 channel的语法
channel在创建时,可以设置一个可选参数:缓冲区容量
- 创建有缓冲channel:
make(chan int, 10)
,创建一个缓冲长度为10的channel - 创建无缓冲channel:
make(chan int)
,其实就是第二个参数为0
channel内可以存储多种数据类型,如下所示:
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
从管道中读取,或者向管道写入数据,使用运算符:<-
,他在channel的左边则是读取,右边则代表写入:
ch := make(chan int)
ch <- 10 // 写入数据10
num := <- ch // 读取数据
2.1 无缓冲channel
无缓冲的channel是阻塞读写的,必须写端与读端同时存在,写入一个数据,则能读出一个数据:
package main
import (
"fmt"
"time"
)
// 写端
func write(ch chan int) {
ch <- 100
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 200
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 300 // 该处数据未读取,后续操作直接阻塞
fmt.Printf("ch addr:%v\n", ch) // 没有输出
}
// 读端
func read(ch chan int) {
// 只读取两个数据
fmt.Printf("取出的数据data1:%v\n", <-ch) // 100
fmt.Printf("取出的数据data2:%v\n", <-ch) // 200
}
func main() {
var ch chan int // 声明一个无缓冲的channel
ch = make(chan int) // 初始化
// 向协程中写入数据
go write(ch)
// 向协程中读取数据
go read(ch)
// 防止主go程提前退出,导致其他协程未完成任务
time.Sleep(time.Second * 3)
}
注意:无缓冲通道的收发操作必须在不同的两个goroutine
间进行,因为通道的数据在没有接收方处理时,数据发送方会持续阻塞,所以通道的接收必定在另外一个 goroutine 中进行。
如果不按照该规则使用,则会引起经典的Golang错误fatal error: all goroutines are asleep - deadlock!
:
func main() {
ch := make(chan int)
ch <- 10
<-ch
}
2.2 有缓存channel
有缓存的channel是非阻塞的,但是写满缓冲长度后,也会阻塞写入。
package main
import (
"fmt"
"time"
)
// 写端
func write(ch chan int) {
ch <- 100
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 200
fmt.Printf("ch addr:%v\n", ch) // 输出内存地址
ch <- 300 // 写入第三个,造成阻塞
fmt.Printf("ch addr:%v\n", ch) // 没有输出
}
func main() {
var ch chan int // 声明一个有缓冲的channel
ch = make(chan int, 2) // 可以写入2个数据
// 向协程中写入数据
go write(ch)
// 防止主go程提前退出,导致其他协程未完成任务
time.Sleep(time.Second * 3)
}
同样的,当数据全部读取完毕后,再次读取也会造成阻塞,如下所示:
func main() {
ch := make(chan int, 1)
ch <- 10
<-ch
// <-ch
}
此时程序可以顺序运行,不会报错,这是与无缓冲通道的区别,但是当继续打开 注释 部分代码时,通道阻塞,所有协程挂起,此时也会产生错误:fatal error: all goroutines are asleep - deadlock!
。
2.3 总结 无缓冲通道与有缓冲通道
无缓冲channel:
- 通道的容量为0,即
cap(ch) = 0
- 通道的个数为0,即
len(ch) = 0
- 可以让读、写两端具备并发同步的能力
有缓冲channel:
- 在make创建的时候设置非0的容量值
- 通道的个数为当前实际存储的数据个数
- 缓冲区具备数据存储的能力,到达存储上限后才会阻塞,相当于具备了异步的能力
- 有缓冲channel的阻塞产生条件:
- 当缓冲通道被填满时,尝试再次发送数据会发生阻塞
- 当缓冲通道为空时,尝试接收数据会发生阻塞
问题:为什么 Go语言对通道要限制长度而不提供无限长度的通道?
channel是在两个 goroutine 间通信的桥梁。使用 goroutine 的代码必然有一方提供数据,一方消费数据 。通道如果不限制长度,在生产速度大于消费速度时,内存将不断膨胀直到应用崩溃。