Go语言学习之 死锁

366 阅读3分钟

前端尝试转GO学习(第九天)

操作系统对死锁的定义

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁的发生必须具备以下四个必要条件

互斥条件: 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

请求和保持条件: 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不剥夺条件: 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件: 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

Go 中的死锁

在Go的协程里面死锁通常就是永久阻塞了

1. 无缓存能力的管道

无缓存能力的管道,写入数据,然后读取管道数据。

func main() {
    ch1 := make(chan int, 0)
    // 这里会发生一直阻塞的情况,执行不到下面一句
    ch1 <- 1
    res := <- ch1
    fmt.Println(res)
}

// fatal error: all goroutines are asleep - deadlock!

2. 协程来晚了

2个以上的 go 程中,使用同一个 channel 通信,读写channel 先于 go 程创建。

func main() {
   ch2 := make(chan int, 0)
   ch2 <- 1
   // 这里虽然创建了子go程用来读出数据,但是上面会一直阻塞运行不到下面
   go func() {
      <-ch2
   }()
}

// fatal error: all goroutines are asleep - deadlock!

3. 互相等对方造成死锁

使用多个 channel 通信。 A go 程 获取channel 1 的同时,尝试使用channel 2, 同一时刻,B go 程 获取channel 2 的同时,尝试使用channel 1。

func main() {
   ch3 := make(chan int, 0)
   ch4 := make(chan int, 0)

   go func() {
      select {
      case <-ch3:
         ch4 <- 1
      }
   }()

   select {
   case <-ch4:
      ch3 <- 1
   }
}

// fatal error: all goroutines are asleep - deadlock!

4. 读写锁相互阻塞,形成隐形死锁

在 go 语言中, channel 和 读写锁、互斥锁 尽量避免交叉混用。——“隐形死锁”。

func main() {
    var rmw09 sync.RWMutex
    ch := make(chan int,0)

    go func() {
        rmw09.Lock()
        ch <- 123
        rmw09.Unlock()
    }()

    go func() {
        rmw09.RLock()
        x := <- ch
        fmt.Println("读到",x)
        rmw09.RUnlock()
    }()

    for {
        runtime.GC()
    }
}

// fatal error: all goroutines are asleep - deadlock!

这两条协程,如果第一条协程先抢到了只写锁,另一条协程就不能抢只读锁了,那么因为另外一条协程没有读,所以第一条协程就写不进。

如果第二条协程先抢到了只读锁,另一条协程就不能抢只写锁了,那么因为另外一条协程没有写,所以第二条协程就读不到。

参考文章:

go的几种死锁情况分析

4种Golang并发操作中常见的死锁情形


本文正在参加技术专题18期-聊聊Go语言框架