golang 死锁情况总结
1. 什么是死锁?
死锁并不是锁的一种,而是一种错误使用锁导致的现象,死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。系统发生死锁现象不仅浪费大量的系统资源,甚至导致整个系统崩溃,带来灾难性后果。所以,对于死锁问题在理论上和技术上都必须予以高度重视。
2.死锁的机制
必须予以高度重视。在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待、资源共享、非抢占式,在并发中出现通道死锁只有两种情况:
数据要发送,但是没有人接收
数据要接收,但是没有人发送
3.死锁情形
3.1.单协程自己死锁
fatal error: all goroutines are asleep - deadlock!
package main
import (
"fmt"
)
func main() {
// 没有人接收/后者接收者在程序后面,死锁
// 这种方式可以演变成更多类型,比如读写锁相互阻塞,形成隐形死锁;管道读写时,相互要求对方先读/写等
{
ch := make(chan int)
ch <- 10
x := <- ch
fmt.Println(x)
}
}
跨主子多协程不死锁
package main
import (
"fmt"
"time"
)
func main() {
// 跨主协程/子协程通信不死锁
{
ch1 := make(chan string)
go func() {
ch1 <- "ch1 value"
}()
<- ch1
fmt.Println("111222222")
}
子协程不会死锁
package main
import (
"fmt"
)
func main() {
{
go func(){
ch := make(chan int)
ch <- 10
x := <- ch
fmt.Println(x)
}()
time.Sleep(3 * time.Second)
fmt.Println("finished")
}
3.2.发送个数超过缓冲区个数死锁
fatal error: all goroutines are asleep - deadlock!
package main
import (
)
func main() {
ch := make(chan int, 1)
ch <- 10
ch <- 11
}
3.3.select没有任何处理,死锁
fatal error: all goroutines are asleep - deadlock!
package main
import (
)
func main() {
select{
}
}
3.4.多channel相互交叉依赖,死锁
fatal error: all goroutines are asleep - deadlock!此时主协程和子协程,互相掌握着各自的条件,但是都无法释放,造成死锁
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for {
select {
case num:=<-ch1:
ch2 <- num
}
}
}()
for {
select {
case num:=<-ch2:
ch1 <- num
}
}
}
3.5.将 互斥锁、读写锁 与 channel 混用。 —— 隐性死锁
下面代码造成隐形死锁的原因是,进行readGo函数时,以读的模式加锁,同时 channel 等待数据写入,以便于数据的读出,此时channel等到数据写入造成堵塞,下面代码无法执行,造成以读模式的锁无法解锁。但进入到writeGo函数中时,向下执行时,遇到以写模式加锁时会造成堵塞,因为上方以读的模式的锁并没有解锁,造成以写模式加锁下方的代码无法执行,无法向channel中写入数据,使其互相堵塞,但是控制台并不报错,该死锁称为隐性死锁。
简单地说,读写锁就是一种能保证:
- 并发读操作之间不互斥;
- 并发写操作之间互斥;
- 并发读操作和写操作互斥;
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var rwMutex sync.RWMutex
//读时共享,写时独占
func readGo(in <- chan int,idx int) {
for {
rwMutex.RLock() //以读模式加锁
num:=<-in
fmt.Println("----%dth 读go程,读出:%d\n",idx,num)
rwMutex.RUnlock() //以读模式解锁
}
}
func writeGo(out chan <- int,idx int) {
for {
num := rand.Intn(1000)
rwMutex.Lock() //以写模式加锁
out <- num
fmt.Println("%dth 写Go程,写入:%d\n",idx,num)
time.Sleep(time.Millisecond * 300)
rwMutex.Unlock() //以写模式解锁
}
}
func main() {
ch:=make(chan int)
for i:=0;i<5;i++ {
go readGo(ch,i+1)
}
for i:=0;i<5;i++ {
go writeGo(ch,i+1)
}
for{
time.Sleep(1 * time.Second)
}
}