这个单例实例实现是否存在竞争条件?
Does this singleton instance implementation has a race condition?
有人告诉我 memCacheInstance
有竞争条件,但 go run -race
说不出来。
代码:
type MemCache struct {
data []string
}
var memCacheInstance *MemCache
var memCacheCreateMutex sync.Mutex
func GetMemCache() *MemCache {
if memCacheInstance == nil {
memCacheCreateMutex.Lock()
defer memCacheCreateMutex.Unlock()
if memCacheInstance == nil {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
}
}
return memCacheInstance
}
go race detector 不会检测到每场比赛,但当它检测到时,它总是一个积极的案例。您必须编写模拟活泼行为的代码。
如果从多个 goroutine 调用 GetMemCache()
,则您的示例存在数据竞争。这个简单的例子触发了竞争检测器:
func main() {
go GetMemCache()
GetMemCache()
}
运行它与go run -race .
,输出是:
==================
WARNING: DATA RACE
Read at 0x000000526ac0 by goroutine 6:
main.GetMemCache()
/home/icza/gows/src/play/play.go:13 +0x64
Previous write at 0x000000526ac0 by main goroutine:
main.GetMemCache()
/home/icza/gows/src/play/play.go:18 +0x17e
main.main()
/home/icza/gows/src/play/play.go:28 +0x49
Goroutine 6 (running) created at:
main.main()
/home/icza/gows/src/play/play.go:27 +0x44
==================
Found 1 data race(s)
exit status 66
它有一个竞争,因为 memCacheInstance
变量的第一次读取没有锁定,没有同步。对变量的所有并发访问都必须同步,其中至少有一个访问是写入。
一个简单的解决方法是删除非同步读取:
func GetMemCache() *MemCache {
memCacheCreateMutex.Lock()
defer memCacheCreateMutex.Unlock()
if memCacheInstance == nil {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
}
return memCacheInstance
}
另请注意,要以并发安全的方式仅执行一次某些代码,有 sync.Once
。你可以这样使用它:
var (
memCacheInstance *MemCache
memCacheOnce sync.Once
)
func GetMemCache() *MemCache {
memCacheOnce.Do(func() {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
})
return memCacheInstance
}
另请注意,如果您“立即”初始化变量(在声明时或在包 init()
函数中),则不需要同步(因为包初始化在单个 goroutine 中运行):
var memCacheInstance = &MemCache{
data: make([]string, 0),
}
func GetMemCache() *MemCache {
return memCacheInstance
}
在这种情况下,您也可以选择导出变量,然后就不需要 GetMemCache()
。
有人告诉我 memCacheInstance
有竞争条件,但 go run -race
说不出来。
代码:
type MemCache struct {
data []string
}
var memCacheInstance *MemCache
var memCacheCreateMutex sync.Mutex
func GetMemCache() *MemCache {
if memCacheInstance == nil {
memCacheCreateMutex.Lock()
defer memCacheCreateMutex.Unlock()
if memCacheInstance == nil {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
}
}
return memCacheInstance
}
go race detector 不会检测到每场比赛,但当它检测到时,它总是一个积极的案例。您必须编写模拟活泼行为的代码。
如果从多个 goroutine 调用 GetMemCache()
,则您的示例存在数据竞争。这个简单的例子触发了竞争检测器:
func main() {
go GetMemCache()
GetMemCache()
}
运行它与go run -race .
,输出是:
==================
WARNING: DATA RACE
Read at 0x000000526ac0 by goroutine 6:
main.GetMemCache()
/home/icza/gows/src/play/play.go:13 +0x64
Previous write at 0x000000526ac0 by main goroutine:
main.GetMemCache()
/home/icza/gows/src/play/play.go:18 +0x17e
main.main()
/home/icza/gows/src/play/play.go:28 +0x49
Goroutine 6 (running) created at:
main.main()
/home/icza/gows/src/play/play.go:27 +0x44
==================
Found 1 data race(s)
exit status 66
它有一个竞争,因为 memCacheInstance
变量的第一次读取没有锁定,没有同步。对变量的所有并发访问都必须同步,其中至少有一个访问是写入。
一个简单的解决方法是删除非同步读取:
func GetMemCache() *MemCache {
memCacheCreateMutex.Lock()
defer memCacheCreateMutex.Unlock()
if memCacheInstance == nil {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
}
return memCacheInstance
}
另请注意,要以并发安全的方式仅执行一次某些代码,有 sync.Once
。你可以这样使用它:
var (
memCacheInstance *MemCache
memCacheOnce sync.Once
)
func GetMemCache() *MemCache {
memCacheOnce.Do(func() {
memCacheInstance = &MemCache{
data: make([]string, 0),
}
})
return memCacheInstance
}
另请注意,如果您“立即”初始化变量(在声明时或在包 init()
函数中),则不需要同步(因为包初始化在单个 goroutine 中运行):
var memCacheInstance = &MemCache{
data: make([]string, 0),
}
func GetMemCache() *MemCache {
return memCacheInstance
}
在这种情况下,您也可以选择导出变量,然后就不需要 GetMemCache()
。