在 go
中常常會使用 channel
,進行併發執行子任務,提升執行效率。但一不當心就會踩到 deadlock
的坑,本文就來解析一下常見的死鎖形式和解決方式。golang
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
阻塞默認處理方式。ui
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 3)
select {
case v := <-ch:
fmt.Println(v)
default:
fmt.Println("chan no data")
}
}
複製代碼
channel
產生死鎖package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 1 // 無緩衝在此寫入數據,卻沒有讀數據,阻塞住
fmt.Println(<-ch) // 被上面阻塞,沒法被執行到
}
複製代碼
輸出結果:spa
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
成對存取數據;code
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 1 // 開啓子goroutine寫入數據
}()
fmt.Println(<-ch) // 阻塞住,一旦ch有數據,則讀取成功
}
複製代碼
b. 採用有緩衝 chan
,在容量範圍內不會阻塞;orm
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 1
fmt.Println(<-ch)
}
複製代碼
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)
}
}
複製代碼
解決方式:cdn
a. 增長緩衝容量,保證能知足寫入全部數據;協程
b. 採用 select case default
阻塞默認處理方式(demo
略);
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)
}
複製代碼
【小結】
平常在使用 channel
中,要注意區分有緩衝(buffered channel
,異步隊列-FIFO處理)與無緩衝(unbuffered channel
,同步流入流出)通道的區別,掌握各自適合使用的方式;
出現deadlock
必定是線程/協程之間存在了資源競爭,互相佔用對方須要的資源致使程序永遠不能退出,須要當心可能遇到的坑,也能夠經過加鎖避免。