go語言并發編程:鎖與通道關閉的陷阱
Go語言中,channel和mutex是處理并發問題的利器,但兩者結合使用時,容易出現意想不到的錯誤,例如本文要討論的“panic: send on closed channel”問題。即使使用了mutex鎖,仍然可能出現此錯誤。
問題重現
以下代碼片段演示了這個問題:
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.TODO()) 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, exiting sender") } }(i) } wg.Wait() }
問題根源分析
代碼中,lock.Lock() 和 lock.Unlock() 保證了close(c)操作的原子性,防止多個goroutine同時關閉通道。然而,select語句的非確定性導致問題。即使通道c已關閉,case c
解決方案
為了避免panic,需要在發送數據前檢查通道是否關閉,或者使用上下文機制優雅地關閉goroutine。以下改進后的代碼使用上下文機制:
立即學習“go語言免費學習筆記(深入)”;
package main import ( "context" "fmt" "sync" ) func main() { c := make(chan int, 10) wg := sync.WaitGroup{} ctx, cancel := context.WithCancel(context.TODO()) wg.Add(1) go func() { defer wg.Done() cancel() // 先取消上下文 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 <-ctx.Done(): fmt.Println("Context cancelled, exiting sender") } }(i) } wg.Wait() }
此版本中,我們先取消上下文,再關閉通道。select語句中的case
? 版權聲明
文章版權歸作者所有,未經允許請勿轉載。
THE END
喜歡就支持一下吧
相關推薦