首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Go映射中封装“并发安全”

在Go映射中封装“并发安全”
EN

Stack Overflow用户
提问于 2021-05-23 08:33:02
回答 1查看 155关注 0票数 1

我有一个结构,MyStruct,它包含一个映射。我想使对地图的访问对并发读写是安全的,但我也希望坚持使用基本的Map,而不是使用sync.Map

因此,我在MyStruct方法上创建插入、删除和获取方法,这些方法由互斥对象保护。代码如下所示

代码语言:javascript
复制
type MyStruct struct {
    mu    sync.Mutex
    myMap map[string]string
}

func (myStruct *MyStruct) Add(val string) {
    myStruct.mu.Lock()
    myStruct.myMap[val] = val
    myStruct.mu.Unlock()
}

func (myStruct *MyStruct) Remove(val string) {
    myStruct.mu.Lock()
    delete(myStruct.myMap, val)
    myStruct.mu.Unlock()
}

func (myStruct *MyStruct) Fetch(val string) string {
    myStruct.mu.Lock()
    ret := delete(myStruct.myMap, val)
    myStruct.mu.Unlock()
    return ret
}

到目前一切尚好。

不过,MyStruct的一些客户端也需要遍历myStruct.myMap,这就是我的问题。哪个是最好的设计,使并发安全也循环操作,而不是在MyStruct的方法?目前我看到了两种选择

  1. 使映射myMapMyStruct的互斥myMap公开,并将使循环线程安全的责任转移给客户端。这很简单,但不知何故,MyStruct不太关心它的客户端--
  2. --将所有东西都保密,并添加一个方法,将映射的副本返回给希望安全地使用它的客户端。从“封装”的角度看,这似乎更好,但同时,听起来有点沉重的

还有其他的可能性吗?对哪种设计有更好的建议吗?

EN

回答 1

Stack Overflow用户

发布于 2021-05-23 09:12:40

还有sync.Map,它具有您需要的所有特性。主要的缺点是它不使用静态类型(因为Go中缺少泛型)。这意味着您必须在任何地方键入断言才能像使用常规地图一样使用它。老实说,使用sync.Map并用静态类型重新声明所有方法可能是最简单的,这样客户端就不必担心执行类型断言。如果你不喜欢sync.Map,请看我的其他建议。

首先要提到的一个改进是将sync.Mutex替换为sync.RWMutex。这允许同时执行多个读取操作。然后,将Fetch更改为使用mu.RLock()mu.RUnlock()

用于循环遍历映射

安全地迭代每个值并执行回调(对整个迭代保持锁定)。注意,由于锁定,您不能在回调中调用DeleteAdd,因此我们不能在迭代期间修改映射。否则,在迭代期间修改映射是有效的,请参阅此answer的工作方式。

代码语言:javascript
复制
func (myStruct *MyStruct) Range(f func(key, value string)) {
    myStruct.mu.RLock()
    for key, value := range myStruct.myMap {
        f(key, value)
    }
    myStruct.mu.RUnlock()
}

下面是这个用法

代码语言:javascript
复制
mystruct.Range(func(key, value string) {
    fmt.Println("map entry", key, "is", value)
})

这里是相同的,但是传入带有回调的映射,这样回调函数就可以直接修改映射。此外,在迭代进行修改时,也会更改为正则锁。注意,如果回调保留对映射的引用并将其存储在某个地方,它将有效地破坏您的封装。

代码语言:javascript
复制
func (myStruct *MyStruct) Range(f func(m map[string]string, key, value string)) {
    myStruct.mu.Lock()
    for key, value := range myStruct.myMap {
        f(myStruct.myMap, key, value)
    }
    myStruct.mu.Unlock()
}

这里有一个使用更干净的选项,因为锁定是精心管理的,因此您可以在回调中使用其他锁定函数。

代码语言:javascript
复制
func (myStruct *MyStruct) Range(f func(key, value string)) {
    myStruct.mu.RLock()
    for key, value := range myStruct.myMap {
        myStruct.mu.RUnlock()

        f(key, value)

        myStruct.mu.RLock()
    }
    myStruct.mu.RUnlock()
}

请注意,range代码执行时始终保持读锁,但在f执行时从未持有读锁。这意味着测距是安全的*,但是回调f可以自由地调用任何其他需要锁定的方法,比如Delete

脚注:在我看来,选项3的用法是最干净的,但主要要注意的是,由于它在整个迭代过程中不连续保持锁,这意味着任何迭代都可能受到其他并发修改的影响。例如,如果在映射有5个键的情况下开始迭代,并同时执行其他代码删除键,则不能说迭代是否会看到所有5个键。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67657750

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档