Golang中针对特定键值加锁的场景:内置map搭配互斥锁与sync.Map的性能对比

Golang中针对特定键值加锁的场景:内置map搭配互斥锁与sync.Map的性能对比 我正在使用 map[float64][]custom_data。我的使用场景是:每个键对应的值只能由一个 Go 协程进行添加或删除操作,如果某个键的值([]custom_data)正在被一个 Go 协程使用,其他协程应该等待该操作完成。

据我所知,sync.Map 会锁定整个映射,但我不想锁定整个映射,而是希望锁定特定键的值或自定义数据数组。在这种情况下,我应该使用内置映射还是 sync.Map?

map[float64][]custom_data
8 回复

感谢使用 使用 sync.RWMutex 运行正常,但有时会抛出映射读写失败的错误

更多关于Golang中针对特定键值加锁的场景:内置map搭配互斥锁与sync.Map的性能对比的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您提供的信息。

由于两个或多个 Go 协程可能同时操作同一个键的值,因此需要并发控制。我希望锁定值以避免混乱的操作。

我想了解的是,sync.Map 是锁定整个映射,还是仅锁定特定键的值?

我认为使用标准的同步库是无法实现的。互斥锁机制并不是以特定方式锁定你的变量,而是确保只有一个 goroutine 能够访问你的变量。

如果你仍然想要实现这个功能,可以基于信号量实现其他动态结构来替代映射,让每个结构体拥有自己的标志位,这样每次读写操作都可以检查该标志。

感谢您
我使用了一个带有读写锁的结构体作为映射键的值,它工作正常,但有时会表现异常并抛出"映射读写失败"的错误,这让我很担心

关于如何为你的映射实现并发安全,你比我们更清楚你的使用意图,因此最终决定权在你手中。

不过,这里有一篇文章详细阐述了你可用的多种选择:https://medium.com/[@deckarep](/user/deckarep)/the-new-kid-in-town-gos-sync-map-de24a6bf7c2c

但我很好奇你具体想实现什么功能,因为你可能会发现你的工作甚至不需要并发支持——特别是如果每次只有一个 Go 协程会访问它的话。不过,sync.Map 似乎正好能满足我认为你需要的功能。

我经常使用这种简单的锁机制

var m = &sync.Mutex{} // 甚至作为全局变量

// 在代码的某个地方
m.Lock()
some_name[key_name] = some_value
m.Unlock()

因此,以这种方式访问是安全的(至少我没有遇到过问题)。当然,你也可以使用读锁或写锁。如果出现错误(恐慌?),请确保是由于同步原因而不是其他相关原因造成的。

关于性能,即使在大型映射上,操作通常也很快完成。唯一可能出现的问题是当映射过大时无法及时更新,或者由于过多的锁导致无法及时读取,但这些属于大规模场景,可以通过其他特定方式处理,我认为不适合使用映射来解决。

针对某个键的值必须仅由一个 Go 协程进行添加或删除

这说明读取器/修改器协程是与键绑定的。
但是

如果某个键的值(自定义数据)正在被一个 Go 协程使用,则其他协程应等待其操作完成

这表明它们并非绑定关系,您只是希望避免并发访问导致的数据竞争。

首先使用 sync.RWMutex 来控制映射的访问。
如果性能不够理想,您可以将映射分片为多个子映射,每个子映射拥有独立的锁。

具体取决于您的使用场景,但或许“为每个键创建管理协程,通过通道传递命令(获取/设置/修改)”的模式也能适用。

对于你的场景,使用内置map配合细粒度锁是更合适的选择。sync.Map确实不适合需要针对特定键进行并发控制的场景,因为它提供的是对整个map的原子操作,而不是针对特定键的细粒度锁。

以下是使用内置map配合互斥锁的解决方案:

package main

import (
    "sync"
)

type custom_data struct {
    // 你的自定义数据结构
    Value string
}

type KeyLockMap struct {
    mu    sync.RWMutex
    locks map[float64]*sync.Mutex
    data  map[float64][]custom_data
}

func NewKeyLockMap() *KeyLockMap {
    return &KeyLockMap{
        locks: make(map[float64]*sync.Mutex),
        data:  make(map[float64][]custom_data),
    }
}

// 获取指定键的锁
func (m *KeyLockMap) getLock(key float64) *sync.Mutex {
    m.mu.Lock()
    defer m.mu.Unlock()
    
    if lock, exists := m.locks[key]; exists {
        return lock
    }
    
    lock := &sync.Mutex{}
    m.locks[key] = lock
    return lock
}

// 添加数据到指定键
func (m *KeyLockMap) Add(key float64, value custom_data) {
    lock := m.getLock(key)
    lock.Lock()
    defer lock.Unlock()
    
    m.data[key] = append(m.data[key], value)
}

// 从指定键删除数据
func (m *KeyLockMap) Remove(key float64, index int) {
    lock := m.getLock(key)
    lock.Lock()
    defer lock.Unlock()
    
    if index >= 0 && index < len(m.data[key]) {
        m.data[key] = append(m.data[key][:index], m.data[key][index+1:]...)
    }
}

// 获取指定键的数据
func (m *KeyLockMap) Get(key float64) []custom_data {
    lock := m.getLock(key)
    lock.Lock()
    defer lock.Unlock()
    
    return m.data[key]
}

// 清理指定键的锁(当不再需要该键时调用)
func (m *KeyLockMap) CleanupLock(key float64) {
    m.mu.Lock()
    defer m.mu.Unlock()
    
    delete(m.locks, key)
}

使用示例:

func main() {
    keyLockMap := NewKeyLockMap()
    
    var wg sync.WaitGroup
    
    // 模拟多个协程操作不同键
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            
            key := float64(i % 3) // 3个不同的键
            
            // 添加数据
            keyLockMap.Add(key, custom_data{Value: "data"})
            
            // 获取数据
            data := keyLockMap.Get(key)
            _ = data
            
            // 删除第一个元素
            if len(data) > 0 {
                keyLockMap.Remove(key, 0)
            }
        }(i)
    }
    
    wg.Wait()
}

这个实现的特点:

  1. 细粒度锁:每个键有独立的互斥锁,只有操作相同键的协程会相互阻塞
  2. 线程安全:使用读写锁保护锁映射的并发访问
  3. 内存管理:提供锁清理机制,避免内存泄漏
  4. 高性能:不同键的操作可以完全并行执行

相比之下,sync.Map在这种场景下的性能会较差,因为:

// 使用sync.Map的示例 - 不推荐
var m sync.Map

// 每次操作都需要锁定整个map
func addData(key float64, value custom_data) {
    actual, _ := m.LoadOrStore(key, &sync.Mutex{})
    lock := actual.(*sync.Mutex)
    lock.Lock()
    defer lock.Unlock()
    
    // 这里还需要额外的同步来操作slice
}

对于你的特定需求,内置map配合细粒度锁的方案在性能和功能上都优于sync.Map

回到顶部