
Go语言加锁代码偶尔出现panic: send on closed channel的原因分析
在Go语言并发编程中,使用锁(mutex)保证线程安全是常见做法,但即使使用了锁,仍然可能遇到panic: send on closed channel错误。本文分析此问题出现的原因及解决方案。
问题代码及现象
以下代码片段演示了该问题:
package mainimport ( "context" "fmt" "sync")var lock sync.Mutexfunc 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 处panic,因为select语句的非确定性行为。
问题分析
Go语言select语句具有非确定性:如果多个case都准备好接收或发送,select会随机选择一个执行。
关键在于:
close(c)和c 的竞争: close(c)操作和c 操作并非原子操作,存在竞争条件。即使加了锁,close(c)操作可能在c 操作之后执行,导致c 尝试向已关闭的通道发送数据,从而引发panic。
select语句的随机性: 即使ctx.Done()已经准备好,select仍然可能随机选择c 执行。
解决方案
为了避免此问题,需要确保在发送数据前检查通道是否已关闭。 可以使用select语句的默认case来实现:
select {case c <- i: fmt.Printf("sent %dn", i)default: fmt.Println("channel closed or full")}
或者,使用一个额外的通道来协调关闭操作:
package mainimport ( "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错误,即使在并发环境下使用锁。 选择哪种解决方案取决于具体的应用场景和代码复杂度。
以上就是为什么加了锁的代码偶尔还会导致panic: send on closed channel?的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1386690.html
微信扫一扫
支付宝扫一扫