Go语言加锁代码偶尔出现
的原因分析
在Go语言并发编程中,使用锁(mutex)保证线程安全是常见做法,但即使使用了锁,仍然可能遇到
错误。本文分析此问题出现的原因及解决方案。
问题代码及现象
以下代码片段演示了该问题:
尽管使用了
和
保护临界区,但程序仍然可能在
处panic,因为
语句的非确定性行为。
问题分析
Go语言
语句具有非确定性:如果多个case都准备好接收或发送,
会随机选择一个执行。
关键在于:
和的竞争:
操作和
操作并非原子操作,存在竞争条件。即使加了锁,
操作可能在
操作之后执行,导致
尝试向已关闭的通道发送数据,从而引发panic。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
语句的随机性:
即使
已经准备好,
仍然可能随机选择
执行。
解决方案
为了避免此问题,需要确保在发送数据前检查通道是否已关闭。 可以使用
语句的默认case来实现:
或者,使用一个额外的通道来协调关闭操作:
这个改进的版本使用
通道来通知goroutine通道即将关闭,避免了竞争条件。
通过以上方法,可以有效地避免
错误,即使在并发环境下使用锁。 选择哪种解决方案取决于具体的应用场景和代码复杂度。
panic: send on closed channelpanic: send on closed channelpackage 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 %d\n", i)
case <-ctx.Done():
fmt.Println("context cancelled")
}
}(i)
}
wg.Wait()
}lock.Lock()lock.Unlock()c <- iselectselectselectclose(c)c <- iclose(c)c <- iclose(c)c <- ic <- iselectctx.Done()selectc <- iselectselect {
case c <- i:
fmt.Printf("sent %d\n", 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 %d\n", i)
case <-done:
fmt.Println("channel closing")
}
}(i)
}
wg.Wait()
}donepanic: send on closed channel