本期分享:
1.循環依賴導致棧溢出
2.無法捕獲子協程的panic
循環依賴導致棧溢出
在Go語言開發中,我們經常會遇到結構體之間需要相互引用的情況。當兩個結構體直接或間接地相互包含對方作為自己的字段時,就會形成循環依賴。
但是在Go語言中直接進行結構體的相互引用會默認不符合語法,因此我們就使用接口進行引用。
代碼示例:
結構體A
type A struct {Name stringHi Hi
}type Say interface {Say()
}func (a *A) Say() {fmt.Println(a.Name, " say Hi")
}func NewA(name string) *A {return &A{Name: name,Hi: NewB("B"),}
}
結構體B
type B struct {Name stringSay Say
}type Hi interface {Hi()
}func (b *B) Hi() {fmt.Println("Hi ", b.Name)
}func NewB(name string) *B {return &B{Name: name,Say: NewA("A"),}
}
當調用NewA(“A”)時,程序會立即崩潰并報錯:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
錯誤原因分析:
-
無限遞歸初始化:NewA調用NewB,NewB又調用NewA,從而形成無限循環調用鏈。
-
棧空間耗盡:每次函數調用都會占用棧空間,無限遞歸導致棧空間被耗盡,最終觸發棧溢出錯誤
解決方案
方案1:打破初始化循環
func NewA(name string) *A {b := &B{Name: "B"} // 先創建B實例a := &A{Name: name,Hi: b, // 直接賦值}b.Say = a // 后設置B的Say字段return a
}
方案2:使用接口+延遲設置
type A struct {Name stringHi Hi // 使用接口類型
}type B struct {Name stringSay Say // 使用接口類型
}// 初始化時先創建實例,后設置字段
a := &A{Name: "A"}
b := &B{Name: "B"}
a.Hi = b
b.Say = a
方案3:重新設計結構
考慮是否真的需要雙向依賴,可以將共用邏輯提取到第三個結構體。
Go語言中的循環依賴問題看似簡單,但可能導致嚴重的運行時錯誤。通過本文的分析和解決方案,我們可以更安全地處理對象間的復雜關系
無法捕獲子協程的panic
在Go語言中,父協程默認情況下不能直接捕獲子協程的panic。這是由Go的并發模型和goroutine的設計決定的:
func Run() {defer func() {if r := recover(); r != nil {log.Printf("ping panic: %v", r) // 這個recover只能捕獲當前goroutine的panic}}()go func() {panic("panic")}()time.Sleep(time.Second * 3)
}
原因如下:
-
獨立的執行棧:每個goroutine都有自己的調用棧,panic和recover機制是基于當前goroutine的調用棧的
-
設計哲學:Go的設計是讓每個goroutine自己處理自己的錯誤,而不是由父goroutine來管理
-
并發安全:如果允許跨goroutine捕獲panic,會導致復雜的并發問題
正確寫法:
func Run() {go func() {defer func() {if r := recover(); r != nil {log.Printf("ping panic: %v", r)}}()panic("panic")}()time.Sleep(time.Second * 3)
}
本篇結束~