如何在指针变量周围使用互斥锁来消除数据竞争。

huangapple go评论103阅读模式
英文:

How to eliminate Data Race even with Mutex Locks around pointer variable

问题

一些初始代码在这里,

  1. func (chm *ConcurrentHashMap) NFetchWorker() {
  2. for {
  3. key := <-NFetchWorkerPipe
  4. chm.mu.RLock()
  5. data := chm.data[string(key)]
  6. chm.mu.RUnlock()
  7. if data.IsUsingNFetch {
  8. chm.mu.Lock()
  9. *(chm.data[string(key)].NFetch)--
  10. chm.mu.Unlock()
  11. }
  12. }
  13. }
  14. go NFetchWorker()

ConcurrentHashMap 结构体如下,

  1. type ConcurrentHashMap struct {
  2. data map[string]DataBlock
  3. mu sync.RWMutex
  4. }

DataBlock 结构体如下,

  1. type DataBlock struct {
  2. ...
  3. NFetch *int32
  4. IsUsingNFetch bool
  5. ...
  6. }

现在当我尝试使用 race 标志运行测试时,我得到以下警告,

  1. Write at 0x00c00012e310 by goroutine 8:
  2. (*ConcurrentHashMap).NFetchWorker()

行号指向以下代码行,

  1. *(chm.data[string(key)].NFetch)--

然而,如果我在 DataBlock.NFetch 中不使用指针,我就不会遇到这个问题。
但是,如果我这样做,我就失去了直接从映射中更改 NFetch 值的能力,而不需要重新分配一个新的结构体对象给映射中的哈希值,这在计算上相对昂贵。我想在不重新分配整个结构体的情况下更改 NFetch 的值,以避免数据竞争。有什么解决方案吗?

对于 Golang 来说还比较新手,我可能在这里做了一些非常愚蠢的事情,请随时指出。

更新:添加读取和写入操作函数

  1. func (chm *ConcurrentHashMap) Get(key Key) Value {
  2. chm.mu.RLock()
  3. fetch, ok := chm.data[string(key)]
  4. chm.mu.RUnlock()
  5. if ok {
  6. if CheckValueValidity(&fetch) {
  7. NFetchWorkerPipe <- key
  8. return fetch.Value
  9. } else {
  10. chm.mu.Lock()
  11. delete(chm.data, string(key))
  12. chm.mu.Unlock()
  13. }
  14. }
  15. return constants.NIL
  16. }

写入操作

  1. func (chm *ConcurrentHashMap) Insert(key Key, value Value, options Options) {
  2. ...
  3. chm.mu.Lock()
  4. chm.data[string(key)] = Block{
  5. Value: value,
  6. NFetch: nFetchOld,
  7. Expiry: time.Now().Add(delay),
  8. IsUsingNFetch: foundNFetch,
  9. IsUsingExpiry: foundTTL,
  10. mu: mutex,
  11. }
  12. chm.mu.Unlock()
  13. }
英文:

Some starter code goes here,

  1. func (chm *ConcurrentHashMap) NFetchWorker() {
  2. for {
  3. key := &lt;-NFetchWorkerPipe
  4. chm.mu.RLock()
  5. data := chm.data[string(key)]
  6. chm.mu.RUnlock()
  7. if data.IsUsingNFetch {
  8. chm.mu.Lock()
  9. *(chm.data[string(key)].NFetch)--
  10. chm.mu.Unlock()
  11. }
  12. }
  13. }
  14. go NFetchWorker()

Struct ConcurrentHashMap looks like this,

  1. type ConcurrentHashMap struct {
  2. data map[string]DataBlock
  3. mu sync.RWMutex
  4. }

Struct DataBlock looks like this,

  1. type DataBlock struct {
  2. ...
  3. NFetch *int32
  4. IsUsingNFetch bool
  5. ...
  6. }

Now when I try to run tests with race flag enabled. I get,

  1. Write at 0x00c00012e310 by goroutine 8:
  2. (*ConcurrentHashMap).NFetchWorker()

The line numbers point to this line.

  1. *(chm.data[string(key)].NFetch)--

However I don't face this issue when I do not use pointer in DataBlock.NFetch.
But if I do that I lose the ability to make changes to NFetch directly from map without reassigning a whole new struct object to that hash in map, which would be relatively computationally expensive. I want to change the value of NFetch without reassigning the whole struct again for one small change while being free from DATA RACE. Any solutions??

Fairly New to Golang, I could be doing something really stupid here, feel free to point it out.

UPDATE: Adding Read and Write Op function

  1. func (chm *ConcurrentHashMap) Get(key Key) Value {
  2. chm.mu.RLock()
  3. fetch, ok := chm.data[string(key)]
  4. chm.mu.RUnlock()
  5. if ok {
  6. if CheckValueValidity(&amp;fetch) {
  7. NFetchWorkerPipe &lt;- key
  8. return fetch.Value
  9. } else {
  10. chm.mu.Lock()
  11. delete(chm.data, string(key))
  12. chm.mu.Unlock()
  13. }
  14. }
  15. return constants.NIL
  16. }

Write Op

  1. func (chm *ConcurrentHashMap) Insert(key Key, value Value, options Options) {
  2. ...
  3. chm.mu.Lock()
  4. chm.data[string(key)] = Block{
  5. Value: value,
  6. NFetch: nFetchOld,
  7. Expiry: time.Now().Add(delay),
  8. IsUsingNFetch: foundNFetch,
  9. IsUsingExpiry: foundTTL,
  10. mu: mutex,
  11. }
  12. chm.mu.Unlock()
  13. }

答案1

得分: 2

问题出在CheckValueValidity函数中,你在没有锁定的情况下访问了NFetch。指针在没有锁定的情况下不应该被访问。

以下是翻译好的代码:

  1. func CheckValueValidity(value *Block) bool {
  2. if value.IsUsingExpiry && !value.Expiry.After(time.Now()) {
  3. return false
  4. }
  5. if value.IsUsingNFetch && *(value.NFetch) <= 0 {
  6. return false
  7. }
  8. return true
  9. }

这段代码应该可以工作:

  1. func (chm *ConcurrentHashMap) Get(key Key) Value {
  2. chm.mu.RLock()
  3. fetch, ok := chm.data[string(key)]
  4. isValid := CheckValueValidity(&fetch)
  5. chm.mu.RUnlock()
  6. if ok {
  7. if isValid {
  8. NFetchWorkerPipe <- key
  9. return fetch.Value
  10. } else {
  11. chm.mu.Lock()
  12. delete(chm.data, string(key))
  13. chm.mu.Unlock()
  14. }
  15. }
  16. return constants.NIL
  17. }
英文:

The problem is in the CheckValueValidity where you are accessing NFetch without locking it. Pointers should never be accessed without locking them.

  1. func CheckValueValidity(value *Block) bool {
  2. if value.IsUsingExpiry &amp;&amp; !value.Expiry.After(time.Now()) {
  3. return false
  4. }
  5. if value.IsUsingNFetch &amp;&amp; *(value.NFetch) &lt;= 0 {
  6. return false
  7. }
  8. return true
  9. }

This code should work

  1. func (chm *ConcurrentHashMap) Get(key Key) Value {
  2. chm.mu.RLock()
  3. fetch, ok := chm.data[string(key)]
  4. isValid := CheckValueValidity(&amp;fetch)
  5. chm.mu.RUnlock()
  6. if ok {
  7. if isValid {
  8. NFetchWorkerPipe &lt;- key
  9. return fetch.Value
  10. } else {
  11. chm.mu.Lock()
  12. delete(chm.data, string(key))
  13. chm.mu.Unlock()
  14. }
  15. }
  16. return constants.NIL
  17. }

huangapple
  • 本文由 发表于 2021年9月15日 00:21:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/69181316.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定