在同一个Go协程中使用Lock和Rlock

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

Using Lock and Rlock in the same go routine

问题

我正在使用Go语言中的RWMutex进行实验,并意识到使用以下代码可能会出现这种行为:

  • goroutine 1 - RLock
  • goroutine 1 - RUnlock
  • goroutine 2 - RLock
  • goroutine 2 - RUnlock
  • goroutine 2 - Lock
  • goroutine 2 - Unlock
  • goroutine 1 - Lock
  • goroutine 1 - Unlock
package main

import (
	"fmt"
	"sync"
	"time"
)

type RLockAndLockStruct struct {
	mu sync.RWMutex

	mapEx map[string]string
}

func main() {
	r := &RLockAndLockStruct{}
	r.mapEx = make(map[string]string)

	go r.RLockAndLockTest("test", "goroutine 1 - ")
	go r.RLockAndLockTest("test", "goroutine 2 - ")
	time.Sleep(4000 * time.Millisecond)
}

func (r *RLockAndLockStruct) RLockAndLockTest(value string, goroutine string) string {
	r.mu.RLock()
	fmt.Printf("%sRLock\n", goroutine)
	t := r.mapEx[value]
	r.mu.RUnlock()
	fmt.Printf("%sRUnlock\n", goroutine)
	if len(t) <= 0 {
		time.Sleep(500 * time.Millisecond)
		r.mu.Lock()
		fmt.Printf("%sLock\n", goroutine)
		r.mapEx[value] = value
		r.mu.Unlock()
		fmt.Printf("%sUnlock\n", goroutine)
		return r.mapEx[value]
	}
	return t
}

我在一些文章中读到,使用RWMutex的RLock进行读取,使用Lock进行写入是使用Goroutines的map的正确方式。然而,如上面的代码所示,如果两个Goroutines几乎同时开始,可能会出现两次写入map而不是一次写入和一次读取。

基于此,我有一些问题:

  1. 是否有办法确保只有一个Goroutine在map上写入(进入上面的if代码块),而所有其他例程都使用新值读取该map(避免进入if代码块)?
  2. 这是正确的Goroutine和map实现吗?
英文:

I am working in an experiment using RWMutex in Go, and I realized that is possible to have this behavior with the follow code:

  • goroutine 1 - RLock
  • goroutine 1 - RUnlock
  • goroutine 2 - RLock
  • goroutine 2 - RUnlock
  • goroutine 2 - Lock
  • goroutine 2 - Unlock
  • goroutine 1 - Lock
  • goroutine 1 - Unlock
package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

type RLockAndLockStruct struct {
	mu sync.RWMutex

	mapEx map[string]string
}

func main() {
	r := &amp;RLockAndLockStruct{}
	r.mapEx = make(map[string]string)

	go r.RLockAndLockTest(&quot;test&quot;, &quot;goroutine 1 - &quot;)
	go r.RLockAndLockTest(&quot;test&quot;, &quot;goroutine 2 - &quot;)
	time.Sleep(4000 * time.Millisecond)
}

func (r *RLockAndLockStruct) RLockAndLockTest(value string, goroutine string) string {
	r.mu.RLock()
	fmt.Printf(&quot;%sRLock\n&quot;, goroutine)
	t := r.mapEx[value]
	r.mu.RUnlock()
	fmt.Printf(&quot;%sRUnlock\n&quot;, goroutine)
	if len(t) &lt;= 0 {
		time.Sleep(500 * time.Millisecond)
		r.mu.Lock()
		fmt.Printf(&quot;%sLock\n&quot;, goroutine)
		r.mapEx[value] = value
		r.mu.Unlock()
		fmt.Printf(&quot;%sUnlock\n&quot;, goroutine)
		return r.mapEx[value]
	}
	return t
}

I have read in some articles that the correct way to use map in Goroutines is using RWMutex with RLock to read and Lock to write. However, as you can see in the code above, if two Goroutines starts almost in the same time, it is possible to have two writes in the same map instead of one write and one read.

From that, I have some questions here:

  1. Is there a way that we can guarantee just one goroutine writing on a map (entering the if codeblock above) and all other routines reading that map with the new value (avoid entering the if codeblock)?
  2. Is it the correct implementation for goroutine and maps?

答案1

得分: 7

这是因为你的代码存在竞态条件。你在读取地图时使用了读锁,做出决策后再使用写锁。但不能保证在获取写锁时,你做出决策的条件仍然成立。

正确的方法是在加锁后重新测试条件:

    if len(t) <= 0 {
        time.Sleep(500 * time.Millisecond)
        r.mu.Lock()
        if len(r.mapEx[value]) <= 0 {
          fmt.Printf("%sLock\n", goroutine)
          r.mapEx[value] = value
        }
        r.mu.Unlock()
        return value

请注意上面使用了return value,否则就需要再次访问地图。

英文:

This is because your code has a race condition in it. You read-lock the map to read it, make a decision, and then write-lock it. There is no guarantee that when you get the write lock, the condition you made the decision on still holds.

The correct way would be to retest the condition after locking:

    if len(t) &lt;= 0 {
time.Sleep(500 * time.Millisecond)
r.mu.Lock()
if len(r.mapEx[value])&lt;=0 {
fmt.Printf(&quot;%sLock\n&quot;, goroutine)
r.mapEx[value] = value
}
r.mu.Unlock()
return value

Note the use of return value above, because otherwise it would have to access the map again.

huangapple
  • 本文由 发表于 2021年8月25日 00:43:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/68911126.html
匿名

发表评论

匿名网友

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

确定