这篇文章主要介绍了golang中死锁的触发事件是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇golang中死锁的触发事件是什么文章都会有所收获,下面我们一起来看看吧。
1、Golang中死锁的触发条件
1.1 书上关于死锁的四个必要条件的讲解
发生死锁时,线程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。
必要条件:
如果在一个系统中以下四个条件同时成立,那么就能引起死锁:
互斥:至少有一个资源必须处于非共享模式,即一次只有一个线程可使用。如果另一线程申请该资源,那么申请线程应等到该资源释放为止。
占有并等待:—个线程应占有至少一个资源,并等待另一个资源,而该资源为其他线程所占有。
非抢占:资源不能被抢占,即资源只能被线程在完成任务后自愿释放。
循环等待:有一组等待线程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。
我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。
图示例:
线程1、线程2都尝试获取对方未释放的资源,从而会一直阻塞,导致死锁发生。
1.2 Golang 死锁的触发条件
看完了书上关于死锁的介绍,感觉挺清晰的,但是实际上到了使用或者看代码时,自己去判断是否会发生死锁却是模模糊糊的,难以准确判断出来。所以特意去网上找了些资料学习,特此记录。
golang中死锁的触发条件:
死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。注意:for 死循环不能算在这里,虽然空for循环是实现了阻塞的效果,但是实际上goroutine是处于运行状态的。
1.3 golang 中阻塞的场景
1.3.1 sync.Mutex、sync.RWMutex
golang中的锁是不可重入锁,对已经上了锁的写锁,再次申请锁是会报死锁。上了读锁的锁,再次申请写锁会报死锁,而申请读锁不会报错。
写写冲突,读写冲突,读读不冲突。
func main() { var lock sync.Mutex lock.Lock() lock.Lock() } //报死锁错误
func main() { var lock sync.RWMutex lock.RLock() lock.Lock() } //报死锁错误
func main() { var lock sync.RWMutex lock.RLock() lock.RLock() } //正常执行
1.3.2 sync.WaitGroup
一个不会减少的 WaitGroup 会永久阻塞。
func main() { var wg sync.WaitGroup wg.Add(1) wg.Wait() //报死锁错误 }
1.3.3 空 select
空 select 会一直阻塞。
package main func main() { select { } } //报死锁错误
1.3.4 channel
为 nil 的channel 发送、接受数据都会阻塞。
func main() { var ch chan struct{} ch <- struct{}{} } //报死锁错误
无缓冲的channel 发送、接受数据都会阻塞。
func main() { ch := make(chan struct{}) <- ch } //报死锁错误
channel 缓冲区满了的,继续发送数据会阻塞。
2、死锁案例讲解
2.1 案例一:空 select{}
package main func main() { select { } }
以上面为例子,select 语句会 造成 当前 goroutine 阻塞,但是却无法解除阻塞,所以会导致死锁。
2.2 案例二:从无缓冲的channel接受、发送数据
func main() { ch := make(chan struct{}) //ch <- struct{}{} //发送 <- ch //接受 fmt.Println("main over!") }
发生原因:
上面创建了一个 名为:ch 的channel,没有缓冲空间。当向无缓存空间的channel 发送或者接受数据时,都会阻塞,但是却无法解除阻塞,所以会导致死锁。
解决方案:边接受边读取
package main // 方式1 func recv(c chan int) { ret := <-c fmt.Println("接收成功", ret) } func main() { ch := make(chan int) go recv(ch) // 启用goroutine从通道接收值 ch <- 10 fmt.Println("发送成功") } // 方式2 func main() { ch := make(chan int,1) ch<-1 println(<-ch) }
2.3 案例三:从空的channel中读取数据
package main import ( "fmt" "time" ) func request(index int,ch chan<- string) { time.Sleep(time.Duration(index)*time.Second) s := fmt.Sprintf("编号%d完成",index) ch <- s } func main() { ch := make(chan string, 10) fmt.Println(ch,len(ch)) for i := 0; i < 4; i++ { go request(i, ch) } for ret := range ch{ //当 ch 中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁 fmt.Println(len(ch)) fmt.Println(ret) } }
发生原因:
当 ch 中没有数据的时候,就是从空的channel中接受数据,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁。
解决办法:当数据发送完了过后,close channel
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func request(index int,ch chan<- string) { time.Sleep(time.Duration(index)*time.Second) s := fmt.Sprintf("编号%d完成",index) ch <- s wg.Done() } func main() { ch := make(chan string, 10) for i := 0; i < 4; i++ { wg.Add(1) go request(i, ch) } go func() { wg.Wait() close(ch) }() LOOP: for { select { case i,ok := <-ch: // select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句 if !ok { break LOOP } println(i) default: time.Sleep(time.Second) fmt.Println("无数据") } } }
2.4 案例四:给满了的channel发送数据
func main() { ch := make(chan struct{}, 3) for i := 0; i < 4; i++ { ch <- struct{}{} } }
发生原因:
ch 是一个带缓冲的channel,但是只能缓冲三个struct,当channel满了过后,继续往channel发送数据会阻塞,但是无法解除阻塞,发生死锁。
解决办法:读取channel中的数据
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { ch := make(chan struct{}, 3) go func() { for { select { case i, ok := <- ch: wg.Done() fmt.Println(i) if !ok { return } } } }() for i := 0; i < 4; i++ { wg.Add(1) ch <- struct{}{} } wg.Wait() }