go語言加鎖代碼偶爾出現panic: send on closed channel的原因分析
在Go語言并發編程中,使用鎖(mutex)保證線程安全是常見做法,但即使使用了鎖,仍然可能遇到panic: send on closed channel錯誤。本文分析此問題出現的原因及解決方案。
問題代碼及現象
以下代碼片段演示了該問題:
package main import ( "context" "fmt" "sync" ) var lock sync.Mutex func main() { c := make(chan int, 10) wg := sync.WaitGroup{} ctx, cancel := context.WithCancel(context.Background()) wg.Add(1) go func() { defer wg.Done() lock.Lock() cancel() close(c) lock.Unlock() }() for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() select { case c <- i: fmt.Printf("sent %dn", i) case <-ctx.Done(): fmt.Println("context cancelled") } }(i) } wg.Wait() }
盡管使用了lock.Lock()和lock.Unlock()保護臨界區,但程序仍然可能在c
問題分析
Go語言select語句具有非確定性:如果多個case都準備好接收或發送,select會隨機選擇一個執行。
關鍵在于:
-
close(c)和c close(c)操作和c
-
select語句的隨機性: 即使ctx.Done()已經準備好,select仍然可能隨機選擇c
解決方案
為了避免此問題,需要確保在發送數據前檢查通道是否已關閉。 可以使用select語句的默認case來實現:
select { case c <- i: fmt.Printf("sent %dn", i) default: fmt.Println("channel closed or full") }
或者,使用一個額外的通道來協調關閉操作:
package main import ( "fmt" "sync" ) func main() { c := make(chan int, 10) done := make(chan struct{}) wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() close(done) // Signal that the channel is closing close(c) }() for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() select { case c <- i: fmt.Printf("sent %dn", i) case <-done: fmt.Println("channel closing") } }(i) } wg.Wait() }
這個改進的版本使用done通道來通知goroutine通道即將關閉,避免了競爭條件。
通過以上方法,可以有效地避免panic: send on closed channel錯誤,即使在并發環境下使用鎖。 選擇哪種解決方案取決于具體的應用場景和代碼復雜度。
? 版權聲明
文章版權歸作者所有,未經允許請勿轉載。
THE END