前端尝试转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!
这两条协程,如果第一条协程先抢到了只写锁,另一条协程就不能抢只读锁了,那么因为另外一条协程没有读,所以第一条协程就写不进。
如果第二条协程先抢到了只读锁,另一条协程就不能抢只写锁了,那么因为另外一条协程没有写,所以第二条协程就读不到。
参考文章: