51

golang面试基础系列-解锁deadlock(四)

 4 years ago
source link: https://www.tuicool.com/articles/IBNBFri
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

go 中经常会使用 channel ,进行并发执行子任务,提高执行效率。但一不小心就会踩到 deadlock 的坑,本文就来解析一下常见的死锁形式和解决方式。

1. 直接读取空 chan 产生死锁

package main

import (
  "fmt"
)

func main() {
  ch := make(chan int, 3)
  <-ch
}

输出结果:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
  /home/work/code/golang/src/interview/go/deadlock/test.go:9 +0x56

Process finished with exit code 2

解决方式:

采用 select case default 阻塞默认处理方式。

package main

import (
  "fmt"
)

func main() {
  ch := make(chan int, 3)

  select {
  case v := <-ch:
    fmt.Println(v)
  default:
    fmt.Println("chan no data")
  }
}

2. 阻塞 channel 产生死锁

package main

import "fmt"

func main() {
  ch := make(chan int)

  ch <- 1 // 无缓冲在此写入数据,却没有读数据,阻塞住

  fmt.Println(<-ch) // 被上面阻塞,无法被执行到
}

输出结果:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
  /home/work/code/golang/src/interview/go/deadlock/test02.go:8 +0x59

Process finished with exit code 2

解决方式:

a. 采用开启子协程方式,保证读写 chan 成对存取数据;

package main

import "fmt"

func main() {
  ch := make(chan int)

  go func() {
    ch <- 1 // 开启子goroutine写入数据
  }()

  fmt.Println(<-ch) // 阻塞住,一旦ch有数据,则读取成功
}

b. 采用有缓冲 chan ,在容量范围内不会阻塞;

package main

import "fmt"

func main() {
  ch := make(chan int, 1)

  ch <- 1

  fmt.Println(<-ch)
}

3. 有缓冲 chan 超过容量时产生死锁

package main

import (
  "fmt"
)

func main() {
  ch := make(chan int, 3)
  ch <- 1
  ch <- 2
  ch <- 3
  ch <- 4 // 超过最大容量,阻塞main协程,产生deadlock

  for v := range ch {
    fmt.Println(v)
  }
}

解决方式:

a. 增加缓冲容量,保证能满足写入所有数据;

b. 采用 select case default 阻塞默认处理方式( demo 略);

4. for range 产生死锁

package main

import (
  "fmt"
)

func main() {
  ch := make(chan int, 3)

  ch <- 1
  ch <- 2
  ch <- 3

  // range 一直读取直到chan关闭,否则产生阻塞死锁
  for v := range ch {
    fmt.Println(v)
  }
}

输出结果:

1
2
3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
  /home/work/code/golang/src/interview/go/deadlock/test04.go:15 +0x115

Process finished with exit code 2

解决方式:

a. 显式关闭 channel

b. 开启子协程,主协程 sleep 等待时间后退出;

package main

import (
  "fmt"
  "time"
)

func main() {
  ch := make(chan int, 3)

  ch <- 1
  ch <- 2
  ch <- 3

  close(ch) // 解决方式1:关闭chan

  // range 一直读取直到chan关闭,否则产生阻塞死锁
  // 解决方式2:开启子协程,主协程sleep等待
  go func() {
    for v := range ch {
      fmt.Println(v)
    }
  }()

  time.Sleep(1e9)
}

【小结】

  1. 日常在使用 channel 中,要注意区分有缓冲( buffered channel ,异步队列-FIFO处理)与无缓冲( unbuffered channel ,同步流入流出)通道的区别,掌握各自适合使用的方式;
  2. 出现 deadlock 一定是线程/协程之间存在了资源竞争,互相占用对方需要的资源导致程序永远不能退出,需要小心可能遇到的坑,也可以通过加锁避免。

1460000019898695


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK