channel的死锁

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」。

1 channel的使用

1.1 channel分类

::: tip 分类

  • 无缓冲区 ch := make(chan string)
  • 有缓冲区 ch := make(chan string, 2)

:::

1.2 channel两类的区别

::: tip 区别是什么 1、从声明方式来看,有缓冲带了容量,即后面的数字,这里的2表示信道可以存放两个stirng类型的变量 2、无缓冲信道本身不存储信息,它只负责转手,有人传给它,它就必须要传给别人,如果只有进或者只有出的操作, 都会造成阻塞。有缓冲的可以存储指定容量个变量,但是超过这个容量再取值也会阻塞。 :::

1.3 两种channel的使用举例

::: tip 无缓冲channel

func main() {
    ch := make(chan string)
    go func() {
        ch <- "send"
    }()
     
    fmt.Println(<-ch)
}
复制代码

::: 在主协程中新启一个协程且是匿名函数,在子协程中向通道发送send,通过打印结果, 我们知道在主线程使用<-ch接收到了传给ch的值。

::: tip 有缓冲channel

func main() {
    ch := make(chan string, 2)
    ch <- "first"
    ch <- "second"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
   //输出结果如下
   //first
   //second
}
复制代码

::: channel本身结构是一个先进先出的队列,所以这里输出的顺序如结果所示。 从代码来看这里也不需要重新启动一个goroutine,也不会发生死锁(后面会讲原因)。

2 channel的关闭和遍历

2.1 关闭

channel是可以关闭的。对于无缓冲和有缓冲信道关闭的语法都是一样的。注意信道关闭了,就不能往信道传值了,否则会报错。

close(channelName)
复制代码

2.2 遍历

有缓冲信道是有容量的,所以是可以遍历的,并且支持使用我们熟悉的range遍历。

func main() {
    chs := make(chan string, 2)
    chs <- "first"
    chs <- "second"
 
    for ch := range chs {
        fmt.Println(ch)
    }
}
复制代码

3 死锁

3.1 死锁现场1

下面这两种情况,即无论是向无缓冲信道传值还是取值,都会发生死锁。

func main() {
	// 第一种情况
	ch := make(chan string)
	ch <- "channelValue"

	//第二种情况
	//ch := make(chan string)
	//<-ch
}
复制代码

::: danger 原因分析 如上场景是在只有一个goroutine即主goroutine的,且使用的是无缓冲信道的情况下。 前面提过,无缓冲信道不存储值,无论是传值还是取值都会阻塞。这里只有一个主协程的情况下, 第一种情况是阻塞在传值,第二种情况是阻塞在取值。因为一直卡住主协程,系统一直在等待,所以系统判断为死锁, 最终报deadlock错误并结束程序。 :::

3.2 死锁现场2

紧接着上面死锁现场1的延伸场景,我们提到延伸场景没有死锁是因为主协程发车走了, 所以子协程也只能回家。也就是两者没有耦合的关系。

 func main() {
     ch1 := make(chan string)
     ch2 := make(chan string)
     go func() {
         ch2 <- "ch2 value"
         ch1 <- "ch1 value"
     }()
      
     <- ch1
    //fatal error: all goroutines are asleep - deadlock!
 }
复制代码

::: danger 原因分析 上面的代码不能保证是主线程的<-ch1先执行还是子协程的代码先执行。

如果主协程先执行到<-ch1,显然会阻塞等待有其他协程往ch1传值。终于等到子协程运行了,结果子协程运行ch2 <- "ch2 value"就阻塞了,因为是无缓冲,所以必须有下家接收值才行,但是等了半天也没有人来传值。

所以这时候就出现了主协程等子协程的ch1,子协程在等ch2的接收者,ch1<-“ch1 value”语句迟迟拿不到执行权,于是大家都在相互等待,系统看不下去了,判定死锁,程序结束。

相反执行顺序也是一样。 :::

3.3 死锁现场3

func main() {
    chs := make(chan string, 2)
    chs <- "first"
    chs <- "second"
 
    for ch := range chs {
        fmt.Println(ch)
    }
}
复制代码

::: danger 原因分析 为什么会在输出完chs信道所有缓存值后会死锁呢? 其实也很简单,虽然这里的chs是带有缓冲的信道,但是容量只有两个,当两个输出完之后,可以简单的将此时的信道等价于无缓冲的信道。 显然对于无缓冲的信道只是单纯的读取元素是会造成阻塞的,而且是在主协程,所以和死锁现场1等价,故而会死锁。 :::

4 总结

  • channel是协程之间沟通的桥梁
  • channel分为无缓冲信道和有缓冲信道
  • channel使用时要注意是否构成死锁以及各种死锁产生的原因

猜你喜欢

转载自juejin.im/post/7032892079260827679
今日推荐