- 使用
pprof
工具:
- CPU分析:
- 在代码中导入
net/http/pprof
包,并启动一个HTTP服务器来暴露pprof
端点。例如:
package main
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// 你的其他业务代码
}
- 然后使用`go tool pprof`命令连接到这个端点,获取CPU分析数据。例如:`go tool pprof http://localhost:6060/debug/pprof/profile`。这可以帮助确定哪些函数消耗了大量CPU时间,有可能是卡住的Goroutine正在执行的函数。
- 阻塞分析:
- 同样通过
pprof
,获取阻塞分析数据。运行go tool pprof http://localhost:6060/debug/pprof/block
。这将显示哪些Goroutine被阻塞以及阻塞的原因,可能是在等待通道数据或获取互斥锁等。
- 使用
runtime/debug
包:
- 在代码中合适的位置(例如,在可能卡住的Goroutine附近)调用
debug.Stack()
函数,它会打印当前Goroutine的栈跟踪信息。可以将这些信息记录到日志文件中,便于分析Goroutine在卡住时的执行状态。
- 例如:
package main
import (
"fmt"
"runtime/debug"
)
func someFunction() {
// 业务逻辑
fmt.Println(string(debug.Stack()))
// 更多业务逻辑
}
- 检查通道操作:
- 通道缓冲区:检查通道的缓冲区大小是否合适。如果通道缓冲区过小,可能导致发送或接收操作阻塞。例如,如果一个Goroutine在向一个已满的无缓冲通道发送数据,就会阻塞。
- 死锁检查:确保没有出现通道死锁。例如,两个Goroutine相互等待对方通过通道发送数据,这会导致死锁。可以通过仔细审查通道的发送和接收逻辑,尤其是在复杂的多Goroutine通信场景中。
- 检查互斥锁操作:
- 锁竞争:使用
runtime/race
工具来检测互斥锁的竞争情况。在运行项目时,添加-race
标志,例如:go run -race main.go
。这会检测到哪些地方存在锁竞争,竞争可能导致某个Goroutine长时间等待锁,从而表现为卡住。
- 锁的使用逻辑:检查互斥锁的加锁和解锁逻辑是否正确。例如,确保在获取锁后一定会释放锁,避免出现死锁或某个Goroutine一直持有锁的情况。
- 日志和打印调试:
- 在关键的同步点(如通道操作前后、加锁和解锁处)添加详细的日志打印。例如,记录Goroutine的ID、操作类型(发送/接收、加锁/解锁)以及时间戳等信息。通过分析这些日志,可以了解Goroutine的执行流程和同步操作的顺序,有助于发现异常的阻塞点。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
func someFunction() {
mu.Lock()
fmt.Printf("Goroutine %d locked the mutex\n", getGoroutineID())
// 业务逻辑
mu.Unlock()
fmt.Printf("Goroutine %d unlocked the mutex\n", getGoroutineID())
}
func getGoroutineID() uint64 {
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.ParseUint(idField, 10, 64)
if err != nil {
panic(fmt.Sprintf("can't get goroutine id: %v", err))
}
return id
}