原因1:死锁
- 描述:多个Goroutine相互等待对方释放资源,形成循环等待,导致所有相关Goroutine卡住。例如,两个Goroutine分别持有对方需要的锁并尝试获取对方的锁。
- 解决方案:仔细设计资源获取和释放的顺序,避免循环依赖。可以使用
context
包来设置操作的截止时间,当操作超时时主动取消,避免死锁一直等待。例如:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
ch := make(chan int)
go func() {
select {
case <-ctx.Done():
return
case ch <- 1:
}
}()
select {
case <-ctx.Done():
fmt.Println("操作超时")
case res := <-ch:
fmt.Println("获取到结果:", res)
}
}
原因2:无缓冲通道的收发阻塞
- 描述:当向无缓冲通道发送数据时,如果没有接收方准备好接收,发送操作会阻塞;同样,从无缓冲通道接收数据时,如果没有发送方准备好发送,接收操作会阻塞。如果这种阻塞一直持续,Goroutine就会卡住。
- 解决方案:合理使用带缓冲的通道,给通道设置适当的缓冲区大小,使得发送和接收操作不会立即阻塞。或者在发送/接收操作时使用
select
语句结合default
分支来避免阻塞。例如:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
go func() {
ch <- 1
}()
select {
case res := <-ch:
fmt.Println("获取到结果:", res)
default:
fmt.Println("通道暂时无数据")
}
}
原因3:无限循环且无调度点
- 描述:Goroutine内部执行一个没有退出条件的无限循环,并且循环体中没有任何可以让Go调度器有机会切换到其他Goroutine执行的操作(如I/O操作、系统调用、
runtime.Gosched()
调用等),导致该Goroutine一直占用CPU,其他Goroutine无法执行。
- 解决方案:在循环体中适当的位置添加
runtime.Gosched()
调用,主动让出CPU时间片,让调度器有机会调度其他Goroutine。例如:
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
for {
fmt.Println("执行任务")
runtime.Gosched()
}
}()
// 主Goroutine防止程序退出
select {}
}