目录
[2.1 重复上锁](#2.1 重复上锁)
[2.2 不会减少的 WaitGroup](#2.2 不会减少的 WaitGroup)
[2.3 空select](#2.3 空select)
[2.4 channel](#2.4 channel)
一、死锁
1.golang中死锁的触发条件:
死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。
2.操作系统死锁:
发生死锁时,线程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。
必要条件:
如果在一个系统中以下四个条件同时成立,那么就能引起死锁:
- 互斥:至少有一个资源必须处于非共享模式,即一次只有一个线程可使用。如果另一线程申请该资源,那么申请线程应等到该资源释放为止。
- 占有并等待:---个线程应占有至少一个资源,并等待另一个资源,而该资源为其他线程所占有。
- 非抢占:资源不能被抢占,即资源只能被线程在完成任务后自愿释放。
- 循环等待:有一组等待线程 {P0,P1,...,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,......,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。
我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。
二、Golang死锁场景
2.1 重复上锁
写写冲突,读写冲突,读读不冲突。golang中的锁是不可重入锁,对已经上了锁的写锁,再次申请锁是会报死锁。上了读锁的锁,再次申请写锁会报死锁,而申请读锁不会报错。
案例1:
重复上写锁
Gopackage main import( "sync" ) func main(){ var lock sync.Mutex lock.Lock() lock.Lock() }
结果:死锁
正常情况:
Gofunc main() { var lock sync.RWMutex lock.RLock() lock.RLock() } //正常执行
2.2 不会减少的 WaitGroup
不会减少的 WaitGroup会永久阻塞
案例1:
Gofunc main() { var wg sync.WaitGroup wg.Add(1) wg.Wait() //报死锁错误 }
结果:
2.3 空select
案例:
Gopackage main func main() { select { } } //报死锁错误
结果:
2.4 channel
1.为 nil 的channel 发送、接受数据都会阻塞;
2.无缓冲的channel 发送、接受数据都会阻塞。解决方案:边接受边读取
3.channel 缓冲区满了的,继续发送数据会阻塞。解决办法:读取channel中的数据
4.当 ch 中没有数据的时候,就是从空的channel中接受数据,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁。 解决:当数据发送完了过后,close channel
案例1:
Gofunc main() { var ch chan struct{} ch <- struct{}{} } //报死锁错误
结果:
案例2:
Gofunc main() { ch := make(chan struct{}) <- ch } //报死锁错误
结果: