Go语言的channel和死锁

Go语言的channel和死锁

阻塞(同步)channel–不带缓冲区的channel

阻塞channel要点

默认情况下创建的channel是阻塞和不带缓冲区的,例如:

ch1 := make(chan int)  // 创建一个阻塞的不带缓冲区的channel

通过默认方式创建的channel有以下性质:

  • 发送操作将会阻塞,直到接收端准备好了。
  • 接收操作将会阻塞,直到发送端准备好了。也就是说:若channel中没有数据,接收者将会阻塞。

阻塞channel产生的死锁

  • 死锁的例子

先看下面的程序:

package main
import (
    "fmt"
)

func f1(in chan int) {
    fmt.Println(<-in)
}

func main() {
    out := make(chan int)
    out <- 2    // 阻塞,后面的代码不会执行
    go f1(out)  // 这里不会执行
}

执行结果:

fatal error: all goroutines are asleep - deadlock!

死锁原因分析

为什么会出现死锁呢?分析产生这种错误的原因,有利于我们对死锁的理解。

out := make(chan int)
out <- 2 //执行这一句时,接收端还没有准备好,此时main线程阻塞了
go f1(out)  // 这里不会执行

注意:执行以上两句话时,main协程阻塞,此时不会再往下执行了。由于没有其他goroutine在接收,直接报错。 所以以上程序的 go f1(out) 这一句是不会执行的,即使注释掉也会报同样的错误。

死锁的解决

如何解决该问题呢?就是要保证channel的接收端和发送端的goroutine都能得到执行。针对以上的例子,可以如下修改。

  • 方法1

先启动goroutine,让主goroutine后面的代码得以执行,修改的方式如下:

func main() {
    out := make(chan int)
    go f1(out)  // 先启动goroutine,启动后会阻塞在fmt.Println(<-in)中,但主线程继续执行
    out <- 2    // 由于已经启动了goroutine,所以,这句得到了执行
}
  • 方法2

接收端和发送端都通过goroutine启动,但这种方式要注意和主线程的同步。

func main() {
    out := make(chan int)
    go func(){out <- 2}()  // 启动goroutine,并阻塞
    go f1(out) // 启动goroutine,接收channel的数据
    time.Sleep(1e9) // 主线程等待1秒,等待所有goroutine都完成自己的工作,但这里需要优化
}

若channel的接收和发送都使用goroutine,则代码的顺序无关紧要。

异步channel–带缓冲区的channel

异步channel基础要点

  • 不带缓冲区的channel只能包含一个元素(一条记录),带缓冲区的channel可以包含多条记录
    n := 100
    ch1 := make(chan string, n)  // 此时的ch1,类似一个消息队列,可以容纳100个string类型的元素
  • 向带缓冲区的channel写数据时不会阻塞,直到channel的缓冲区满了
  • 从带缓冲区的channel中读数据也不会阻塞,直到缓冲区为空
  • 从带缓冲区的channel中读取或写入数据时,是异步的,类比使用消息队列写入和读取数据
  • 向带缓冲区的channel中写数据时是FIFO顺序进行的

带缓冲区的channel产生的死锁

产生死锁的代码

func f2(ch chan int) {
    fmt.Println(<-ch)
}

func main() {
    out := make(chan int, 1)  // 创建一个带缓冲区的channel,但只容纳一个元素
    out <- 2    // 向channel中输入一个整数,此时channel满了没有人消费
    out <- 3    // channel已满,此时阻塞

    go f2(out)  // 上一句已阻塞,此句不会执行
}

运行以上程序,可能得到以下结果:

fatal error: all goroutines are asleep - deadlock!

死锁的原因分析

以上代码创建了一个缓冲区的channel,该channel只能容纳一个元素。当执行out<-2时,此时的channel已经满了。当执行下一句out<-3时,由于此时的channel已经满了,而且没有其他协程消费该channel中的元素,此时阻塞,再也没有机会执行下面的语句,从而产生了死锁。

死锁的解决

  • 把channel的缓冲区的容量扩大,让out<-3这一句不会阻塞,代码得以继续往下执行
  out := make(chan int, 10) 
  • 把向channel中写入和消费数据都创建成协程,通过协程进行
  func main() {
    out := make(chan int, 10)
    go func(){
        out <- 2
        out <- 3
        }()  // 启动goroutine,并阻塞

    go f1(out) // 启动goroutine,接收channel的数据
      time.Sleep(2e9) // 主线程等待1秒,等待所有goroutine都完成自己的工作
  }

总结

在使用channel时,当向channel写数据时要保证有goroutine能够读出该数据,否则将会发生死锁。同样的道理,当向channel读数据时,要保证有goroutine向channel中写入数据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值