golang面試基礎系列-解鎖deadlock(四)

go 中常常會使用 channel,進行併發執行子任務,提升執行效率。但一不當心就會踩到 deadlock 的坑,本文就來解析一下常見的死鎖形式和解決方式。golang

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 阻塞默認處理方式。spa

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

解決方式:code

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. 增長緩衝容量,保證能知足寫入全部數據;rem

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必定是線程/協程之間存在了資源競爭,互相佔用對方須要的資源致使程序永遠不能退出,須要當心可能遇到的坑,也能夠經過加鎖避免。

稻草人生

相關文章
相關標籤/搜索