即使使用指针变量周围的互斥锁,如何消除数据竞争
How to eliminate Data Race even with Mutex Locks around pointer variable
这里有一些入门代码,
func (chm *ConcurrentHashMap) NFetchWorker() {
for {
key := <-NFetchWorkerPipe
chm.mu.RLock()
data := chm.data[string(key)]
chm.mu.RUnlock()
if data.IsUsingNFetch {
chm.mu.Lock()
*(chm.data[string(key)].NFetch)--
chm.mu.Unlock()
}
}
}
go NFetchWorker()
Struct ConcurrentHashMap 看起来像这样,
type ConcurrentHashMap struct {
data map[string]DataBlock
mu sync.RWMutex
}
结构数据块看起来像这样,
type DataBlock struct {
...
NFetch *int32
IsUsingNFetch bool
...
}
现在,当我尝试 运行 启用比赛标志的测试时。我明白了,
Write at 0x00c00012e310 by goroutine 8:
(*ConcurrentHashMap).NFetchWorker()
行号指向这一行。
*(chm.data[string(key)].NFetch)--
然而,当我在 DataBlock.NFetch 中不使用指针时,我不会遇到这个问题。
但是,如果我这样做,我将失去直接从 map 对 NFetch 进行更改的能力,而无需将一个全新的结构对象重新分配给 map 中的该哈希,这在计算上会相对昂贵。我想更改 NFetch 的值,而无需为一个小更改再次重新分配整个结构,同时不受 DATA RACE 的影响。有什么解决办法吗??
刚接触 Golang,我可能在这里做了一些非常愚蠢的事情,请随时指出。
更新:添加读写操作函数
func (chm *ConcurrentHashMap) Get(key Key) Value {
chm.mu.RLock()
fetch, ok := chm.data[string(key)]
chm.mu.RUnlock()
if ok {
if CheckValueValidity(&fetch) {
NFetchWorkerPipe <- key
return fetch.Value
} else {
chm.mu.Lock()
delete(chm.data, string(key))
chm.mu.Unlock()
}
}
return constants.NIL
}
写操作
func (chm *ConcurrentHashMap) Insert(key Key, value Value, options Options) {
...
chm.mu.Lock()
chm.data[string(key)] = Block{
Value: value,
NFetch: nFetchOld,
Expiry: time.Now().Add(delay),
IsUsingNFetch: foundNFetch,
IsUsingExpiry: foundTTL,
mu: mutex,
}
chm.mu.Unlock()
}
问题出在 CheckValueValidity
中,您在没有锁定的情况下访问 NFetch
。指针不应该在没有锁定的情况下被访问。
func CheckValueValidity(value *Block) bool {
if value.IsUsingExpiry && !value.Expiry.After(time.Now()) {
return false
}
if value.IsUsingNFetch && *(value.NFetch) <= 0 {
return false
}
return true
}
此代码应该有效
func (chm *ConcurrentHashMap) Get(key Key) Value {
chm.mu.RLock()
fetch, ok := chm.data[string(key)]
isValid := CheckValueValidity(&fetch)
chm.mu.RUnlock()
if ok {
if isValid {
NFetchWorkerPipe <- key
return fetch.Value
} else {
chm.mu.Lock()
delete(chm.data, string(key))
chm.mu.Unlock()
}
}
return constants.NIL
}
这里有一些入门代码,
func (chm *ConcurrentHashMap) NFetchWorker() {
for {
key := <-NFetchWorkerPipe
chm.mu.RLock()
data := chm.data[string(key)]
chm.mu.RUnlock()
if data.IsUsingNFetch {
chm.mu.Lock()
*(chm.data[string(key)].NFetch)--
chm.mu.Unlock()
}
}
}
go NFetchWorker()
Struct ConcurrentHashMap 看起来像这样,
type ConcurrentHashMap struct {
data map[string]DataBlock
mu sync.RWMutex
}
结构数据块看起来像这样,
type DataBlock struct {
...
NFetch *int32
IsUsingNFetch bool
...
}
现在,当我尝试 运行 启用比赛标志的测试时。我明白了,
Write at 0x00c00012e310 by goroutine 8:
(*ConcurrentHashMap).NFetchWorker()
行号指向这一行。
*(chm.data[string(key)].NFetch)--
然而,当我在 DataBlock.NFetch 中不使用指针时,我不会遇到这个问题。 但是,如果我这样做,我将失去直接从 map 对 NFetch 进行更改的能力,而无需将一个全新的结构对象重新分配给 map 中的该哈希,这在计算上会相对昂贵。我想更改 NFetch 的值,而无需为一个小更改再次重新分配整个结构,同时不受 DATA RACE 的影响。有什么解决办法吗??
刚接触 Golang,我可能在这里做了一些非常愚蠢的事情,请随时指出。
更新:添加读写操作函数
func (chm *ConcurrentHashMap) Get(key Key) Value {
chm.mu.RLock()
fetch, ok := chm.data[string(key)]
chm.mu.RUnlock()
if ok {
if CheckValueValidity(&fetch) {
NFetchWorkerPipe <- key
return fetch.Value
} else {
chm.mu.Lock()
delete(chm.data, string(key))
chm.mu.Unlock()
}
}
return constants.NIL
}
写操作
func (chm *ConcurrentHashMap) Insert(key Key, value Value, options Options) {
...
chm.mu.Lock()
chm.data[string(key)] = Block{
Value: value,
NFetch: nFetchOld,
Expiry: time.Now().Add(delay),
IsUsingNFetch: foundNFetch,
IsUsingExpiry: foundTTL,
mu: mutex,
}
chm.mu.Unlock()
}
问题出在 CheckValueValidity
中,您在没有锁定的情况下访问 NFetch
。指针不应该在没有锁定的情况下被访问。
func CheckValueValidity(value *Block) bool {
if value.IsUsingExpiry && !value.Expiry.After(time.Now()) {
return false
}
if value.IsUsingNFetch && *(value.NFetch) <= 0 {
return false
}
return true
}
此代码应该有效
func (chm *ConcurrentHashMap) Get(key Key) Value {
chm.mu.RLock()
fetch, ok := chm.data[string(key)]
isValid := CheckValueValidity(&fetch)
chm.mu.RUnlock()
if ok {
if isValid {
NFetchWorkerPipe <- key
return fetch.Value
} else {
chm.mu.Lock()
delete(chm.data, string(key))
chm.mu.Unlock()
}
}
return constants.NIL
}