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