面试题答案
一键面试可能导致Goroutine卡住的资源竞争原因
- 共享变量竞争:多个Goroutine同时读写共享变量,没有合适的同步机制。例如:
package main
import (
"fmt"
)
var num int
func increment() {
for i := 0; i < 1000; i++ {
num++
}
}
func main() {
for i := 0; i < 10; i++ {
go increment()
}
// 这里如果没有合适的同步等待,可能num还没更新完就结束程序
fmt.Println(num)
}
在此例中,多个Goroutine同时对num
进行写操作,可能导致数据竞争,并且如果没有正确同步,可能会让Goroutine卡住。
2. 通道(Channel)死锁:当Goroutine在通道上发送或接收数据,但没有配对的操作时会发生死锁。例如:
package main
func main() {
ch := make(chan int)
ch <- 1 // 发送数据,但没有Goroutine接收,导致死锁
}
- 锁的滥用或死锁:例如多个Goroutine在获取不同顺序的锁时,可能会形成死锁。
package main
import (
"sync"
)
var (
mu1 sync.Mutex
mu2 sync.Mutex
)
func goroutine1() {
mu1.Lock()
defer mu1.Unlock()
mu2.Lock()
defer mu2.Unlock()
// 业务逻辑
}
func goroutine2() {
mu2.Lock()
defer mu2.Unlock()
mu1.Lock()
defer mu1.Unlock()
// 业务逻辑
}
这里goroutine1
和goroutine2
获取锁的顺序不一致,可能导致死锁,使得Goroutine卡住。
排查资源竞争问题的方法
- 使用
-race
标志:在编译和运行Go程序时,使用-race
标志可以检测数据竞争。例如:
go run -race main.go
如果存在数据竞争,会输出详细的竞争信息,包括竞争发生的位置等。
2. 使用pprof
分析:通过pprof
可以分析程序的性能,包括是否存在阻塞等情况。首先在代码中引入net/http/pprof
包,启动一个HTTP服务器来提供分析数据:
package main
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go http.ListenAndServe(":6060", nil)
// 主程序逻辑
}
然后通过浏览器访问http://localhost:6060/debug/pprof/
,可以查看各种分析数据,比如goroutine
、block
等,从中发现是否存在Goroutine卡住的情况及原因。
3. 添加日志:在关键代码位置添加日志,比如在获取锁、发送/接收通道数据前后打印日志,通过日志来分析程序执行流程,找出可能卡住的位置。例如:
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
func worker() {
fmt.Println("Worker starting, trying to get lock")
mu.Lock()
defer mu.Unlock()
fmt.Println("Worker got lock, doing work")
// 业务逻辑
}