channel 底层原理
1.channel实际上是一个队列,遵守先进先出原则。
2.代码中创建的channel实际上是一个指针,占8个字节,指向堆内存中的chan结构数据, hchan。
3.chan结构的主要部分:
(1) buf :如果channel是有缓冲的,数据存在buf这个循环数组中。
(2) sendx :下一个要发送的数据的下标。
(3) recvx :下一个要接受的数据的下标。
(4) sendq :待发送的数据队列(双向链表)。
(5) recvq :待接受的数据队列(双向链表)。
(6) closed :channel是否关闭标志。
(7) elemtype :channel中的元素类型。
channel的类型,模式,状态
2种类型:有缓冲,无缓冲
无缓冲 | 有缓冲 | |
---|---|---|
创建方式 | c := make(chan int) | c := make(chan int, 5) |
发送 | 如之前有数据没有被接收,发送阻塞 | 只有缓冲区满了,发送才会阻塞 |
接收 | 没有数据过来的话,接收时阻塞的 | 只有缓冲区空了,接收才会阻塞 |
3种模式 : 只写, 只读, 可读可写
c1 := make(chan<- int) // 只写
c2 := make(<-chan int) // 只读
c3 := make(chan int) // 可读可写
3种状态 :未初始化,关闭,正常
状态 / 操作 | 未初始化 | 关闭 | 正常 |
---|---|---|---|
关闭 | panic | panic | 正常关闭 |
发送 | 永远阻塞-死锁 | panic | 阻塞或者发送成功 |
接收 | 永远阻塞-死锁 | 缓冲区有数据就读取,没有就接收该channel类型的零值 | 阻塞或者发送- |
- 多个goroutine监听同一个channel,如果这个channel关闭,所有goroutine都能收到信号。
channel什么情况下会死锁
1.对无缓冲channel只写,不读
func dealLock() {
ch := make(chan int)
ch <- 3 // 给非缓冲channel写入数据,但是没有地方读出去,会死锁
}
2.对无缓冲channel写了,但是读在写的后面
func dealLock() {
ch := make(chan int)
ch <- 3 // 给非缓冲channel写入数据,会阻塞在这里,后面的读取操作无法进行
num := <-ch
fmt.Println(num)
}
3.数据超过channel的缓冲区大小
func dealLock() {
ch := make(chan int,3)
ch <- 1
ch <- 2
ch <- 3
ch <- 4 // 这里阻塞住了,因为channel只有3个缓冲区
}
4.读取无缓冲的空channel
func dealLock() {
ch := make(chan int)
num := <-ch // 阻塞
}
5.多个协程相互等待
func dealLock() {
ch1 := make(chan int)
ch2 := make(chan int)
// 子协程等待读取 ch1,然后往 ch2 写数据
// 主协程等待读取 ch2,然后往 ch1 写数据
// 相互等待,谁也不先发信息,永远等待下去
// 子协程
go func() {
for {
select {
case <-ch1:
fmt.Println("i am son")
ch2 <- 666
}
}
}()
// 主协程
for {
for {
select {
case <-ch2:
fmt.Println("i am son")
ch1 <- 666
}
}
}
}
channel可以干啥?
1.通过channel 控制goroutine执行顺序
var wg sync.WaitGroup
func main() {
// 执行3个协程,创建3个channel
wg.Add(3) // 执行几个协程,就加几个计数器
ch1 := make(chan struct{}, 1)
ch2 := make(chan struct{}, 1)
ch3 := make(chan struct{}, 1)
ch1 <- struct{}{} // 刚开始执行第一个协程,就先给第一个任务channel数据
// 开始顺序执行
go printMsg("g1", ch1, ch2) // 第一个协程执行完,再执行第二个
go printMsg("g2", ch2, ch3)
go printMsg("g3", ch3, ch1)
// 阻塞等待计数器完成
wg.Wait()
}
func printMsg(gName string, inChan chan struct{}, outChan chan struct{}) {
// 模拟执行了 1 秒的任务
time.Sleep(1 * time.Second)
// 监听任务channel
select {
case <-inChan:
fmt.Println("goroutine :", gName) // 这里模拟执行业务
outChan <- struct{}{} // 给下一个任务通道数据
}
// 当上一个协程执行完毕,计数器减 1
wg.Done()
}
顺序执行goroutine,打印:
groutine : g1
groutine : g2
groutine : g3