【问题标题】:Using Lock and Rlock in the same go routine在同一个 go 例程中使用 Lock 和 Rlock
【发布时间】:2021-10-24 21:32:30
【问题描述】:

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

  • goroutine 1 - RLock
  • goroutine 1 - 运行锁定
  • goroutine 2 - RLock
  • goroutine 2 - 运行锁定
  • goroutine 2 - 锁
  • goroutine 2 - 解锁
  • goroutine 1 - 锁
  • goroutine 1 - 解锁
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
}

我在一些文章中读到,在 Goroutines 中使用 map 的正确方法是使用 RWMutex 和 RLock 来读取和 Lock 来写入。但是,正如您在上面的代码中看到的那样,如果两个 Goroutines 几乎同时启动,则可能在同一个 map 中有两次写入,而不是一次写入和一次读取。

因此,我有一些问题:

  1. 有没有一种方法可以保证只有一个 goroutine 写入映射(输入上面的 if 代码块),而所有其他例程使用新值读取该映射(避免输入 if 代码块)?
  2. goroutine 和 maps 的实现是否正确?

【问题讨论】:

    标签: go goroutine


    【解决方案1】:

    这是因为您的代码中存在竞争条件。你读锁地图来阅读它,做出决定,然后写锁它。不能保证当你获得写锁时,你做出决定的条件仍然成立。

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

        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的使用,否则它必须再次访问地图。

    【讨论】:

    • 我认为由于return r.mapEx[value],仍然可能存在并发问题。
    • @nipuna,很好,我将其添加到答案中
    • 当然,那就完美了:-)
    • 不返回值,我们可以使用:r.mu.RLock(); t2 := r.mapEx[value]; r.mu.RUnlock(); return t2r.mu.Unlock()之后吗?
    • 是的,但是这样做有什么好处呢?理想情况下,你会做r.mu.Lock(); defer r.mu.Unlock(),然后返回值,让 defer 负责解锁。请注意,一旦函数返回,该值已经过时。不能保证函数返回后另一个 goroutine 没有改变值。
    猜你喜欢
    • 2014-05-18
    • 1970-01-01
    • 1970-01-01
    • 2016-10-07
    • 2021-10-11
    • 1970-01-01
    • 2016-06-10
    • 1970-01-01
    • 2018-02-18
    相关资源
    最近更新 更多