如何使用RWMutex和升级锁

时间:2018-05-04 00:22:04

标签: go

我试图实现访问允许多个地图的地图的模式 读者和只有一位作家,并且只有一次将关键字写入地图。

我想确保密钥(如果存在)只被放置到地图中一次。然后,一旦添加了密钥,读者就可以从地图中获取值。

它认为这是正确的,虽然主线程可以在所有goroutine完成之前退出,因此我不总是看到10个Exit的打印。这是正确的实施模式吗?

package main

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

var downloads map[string]*sync.WaitGroup
var downloadsLock sync.RWMutex

func concurrentAccessTest() {
    // Acquire the read lock to check if the items is already there.
    downloadsLock.RLock()
    if wg, ok := downloads["item"]; ok {
        // Item exists
        downloadsLock.RUnlock()
        wg.Wait()
    } else {
        downloadsLock.RUnlock()

        fmt.Println("Upgrade Lock")
        // Item does not exist we need to add it to the map
        downloadsLock.Lock()
        fmt.Println("Writer lock obtained")
        // Check another thread hasn't gone down this path and added the item to the map
        if wg, ok := downloads["item"]; ok {
            // Item exists, no need to add it, unlock and wait
            downloadsLock.Unlock()
            wg.Wait()
            fmt.Println("Ok, now we can proceed")
        } else {
            // We are first in. Add and unlock
            wg = &sync.WaitGroup{}
            downloads["item"] = wg
            downloadsLock.Unlock()

            wg.Add(1)

            fmt.Println("Do some time consuming stuff!")
            time.Sleep(5 * time.Second)
            wg.Done()
        }
    }

    fmt.Println("Exit")
}

func main() {
    downloads = make(map[string]*sync.WaitGroup)

    // Add to the map
    for i := 0; i < 10; i++ {
        go concurrentAccessTest()
    }

    concurrentAccessTest()

    // Wait for all threads to exit
    fmt.Println("Done!")
}

2 个答案:

答案 0 :(得分:0)

正如您的代码所示,无法从读取锁定升级到写入锁定,您必须释放读取锁定,然后执行写入锁定。

我认为你很接近,使用RWMutex本身看起来很好,但是我相信你在释放锁之后调用wg.Add(1)会有潜在的问题。有可能其他一个goroutine会从地图中读取WaitGroup并在执行Add()调用之前调用Wait(),根据WaitGroup文档是一个问题。文档说Note that calls with a positive delta that occur when the counter is zero must happen before a Wait.您可以通过在解锁()之前移动wg.Add(1)来轻松解决此问题

答案 1 :(得分:0)

您不是defer的{​​{1}}电话吗?如果是这样,“升级”将更加困难:如果您手动解锁读取锁定以获取写入锁定,则RUnlock()方法将在以后发生问题。这意味着您必须在单独的函数中读取内容(利用读取锁和defer语句)。