go快速上手:并发编程之sync.Map

83 阅读3分钟

Golang 并发编程中的 sync.Map 详解

在 Go 语言(Golang)的并发编程领域,处理共享资源时的线程安全是一个重要议题。传统的做法是使用互斥锁(如 sync.Mutex)来保护共享的数据结构,但这种方式在高频读写场景下可能会成为性能瓶颈。为此,Go 语言标准库在 1.9 版本中引入了 sync.Map,它专为并发环境设计,能够在不牺牲太多性能的前提下,提供线程安全的 map 操作。

为什么需要 sync.Map

在 Go 中,直接使用 map 是不安全的,如果多个 goroutine 同时读写同一个 map,可能会导致运行时 panic。虽然可以使用 sync.Mutexsync.RWMutex 来保护 map,但这在极端并发场景下可能会降低性能,因为互斥锁会阻塞等待锁的 goroutine。而 sync.Map 采用了更加精细的锁控制策略,针对读写操作进行了优化,以减少锁的竞争。

sync.Map 的基本使用

sync.Map 提供了以下几个关键方法:

  • Store(key, value interface{}):存储键值对。如果 key 已存在,它会更新对应的值。
  • Load(key interface{}) (value, bool):根据 key 加载值。如果 key 存在,返回对应的值和 true;否则返回值的零值和 false。
  • LoadOrStore(key, value interface{}) (actual, loaded bool):尝试加载 key 对应的值,如果不存在,则存储键值对。返回实际的值和一个布尔值,指示值是否已加载(而非新存储的)。
  • Delete(key interface{}):删除键值对。
  • Range(f func(key, value interface{}) bool):遍历 sync.Map 中的所有元素。如果 f 返回 false,则遍历会提前结束。

示例代码

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // 存储键值对
    m.Store("foo", "bar")

    // 加载并打印值
    if val, ok := m.Load("foo"); ok {
        fmt.Println(val) // 输出: bar
    }

    // 尝试加载或存储
    if actual, loaded := m.LoadOrStore("foo", "baz"); loaded {
        fmt.Println("Loaded:", actual) // 输出: Loaded: bar
    } else {
        fmt.Println("Stored:", actual)
    }

    // 删除键值对
    m.Delete("foo")

    // 遍历
    m.Store("one", 1)
    m.Store("two", 2)
    m.Range(func(key, value interface{}) bool {
        fmt.Println(key, value)
        return true // 继续遍历
    })
}

package main

import (
	"fmt"
	"sync"
)

func main() {
	var scene sync.Map
	// 将键值对保存到sync.Map
	scene.Store("greece", 97)
	scene.Store("london", 100)
	scene.Store("egypt", 200)
	// 从sync.Map中根据键取值
	fmt.Println(scene.Load("london"))
	// 根据键删除对应的键值对
	scene.Delete("london")
	// 遍历所有sync.Map中的键值对
	scene.Range(func(k, v interface{}) bool {
		fmt.Println("iterate:", k, v)
		return true
	})
}

sync.Map 的适用场景

  • 高并发读写:当多个 goroutine 需要频繁读写同一个 map,且对性能要求较高时。
  • 动态数据集:当数据集大小未知或可能频繁变化时,sync.Map 的动态扩容和缩容机制比手动管理锁更加高效。
  • 读多写少:虽然 sync.Map 对读写都进行了优化,但在读远多于写的场景下表现更佳。

注意事项

  • sync.Map 的性能可能不如直接使用 map 加锁,特别是在低并发或数据量不大的情况下。
  • sync.Map 不支持迭代器的 next 方法,即一旦开始迭代,无法从某个特定点继续迭代。
  • 在迭代过程中,sync.Map 允许并发修改,但迭代函数需要能够处理这种并发性,或者确保在迭代期间不修改 map。

以上就是sync.Map的用法。