首页 > 编程笔记 > Go语言笔记 阅读:10

Go语言sync.Map的用法(非常详细)

在 Go 语言 1.6 版本之前,集合Map在并发程序中支持数据读取,但在写入过程中会存在异常,在 1.6 版本之后,通过并发读写集合 Map 都会提示异常。因此在 1.9 版本之前都是通过加锁处理或者封装成一个新的结构体,具体示例如下:
package main

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

// 定义全局变量
// 定义互斥锁
var s sync.Mutex
// 定义同步等待组
var wg sync.WaitGroup

// 定义并发函数
func set_map(m map[string]int, b int) {
   for i := 1; i < 5; i++ {
        // 加锁处理
        s.Lock()
        m["age"] = i + b
        fmt.Printf("集合map的age数据:%v\n", m["age"])
        // 解锁处理
        s.Unlock()
   }
   // 释放同步等待
   wg.Done()
}

func main() {
   // 记录程序开始时间
   start := time.Now()
   // 设置同步等待组
   wg.Add(2)
   m := map[string]int{"age": 10}
   // 执行并发操作
   go set_map(m, 0)
   go set_map(m, 10)
   // 等待同步等待组
   wg.Wait()
   // 记录程序结束时间并计算执行时间
   end := time.Now()
   consume := end.Sub(start).Seconds()
   fmt.Println("程序执行耗时(s):", consume)
}
运行上述代码,运行结果为:

集合map的age数据:11
集合map的age数据:12
集合map的age数据:13
集合map的age数据:1
集合map的age数据:2
集合map的age数据:3
集合map的age数据:4
集合map的age数据:14
程序执行耗时(s):0.0030746


在 1.9 版本之后,Go 语言提供了一种效率较高且支持并发的数据类型,叫做 sync.Map。它是以结构体方式定义的,设有 4 个结构体成员和 8 个结构体方法,源码为:
type Map struct {
    mu Mutex

    // ...
    read atomic.Value // readOnly

    // ...
    dirty map[interface{}]*entry

    // ...
    misses int
}

// readOnly is an immutable struct stored atomically.
type readOnly struct {...}

// ...
var expunged = unsafe.Pointer(new(interface{}))

// An entry is a slot in the map corresponding to a key.
type entry struct {...}
根据 sync.Map 的定义阐述 sync.Map 的结构体成员和常用结构体方法:
1) 成员 mu 是互斥锁,涉及结构体成员 dirty 的数据操作都要使用该锁进行锁定处理。

2) 成员 read 提供数据只读功能。

3)成员 dirty 是当前集合 map 的数据,执行数据操作会使用结构体成员 mu 进行加锁处理,集合 map 的值为 *entry,它是结构体 entry。

4) 成员 misses 是计数器,它负责处理 read、dirty 的数据,确保两者的数据能同步更新。

5) Load(key interface{}) (value interface{}, ok bool) 方法根据键查找对应值,如果键不存在 sync.Map,其值为 nil(空值)。该方法设有一个参数和两个返回值,参数 key 代表需要查找的键,返回值 value 是键对应的值,返回值 ok 是查找结果。若参数 key 在 sync.Map 中,则返回 true,否则返回 false。

6) Store(key, value interface{}) 方法新增或修改一个键值对。参数 key 是新增或修改的键,参数 value 代表键对应的值。如果参数 key 不在 sync.Map 中,则执行新增操作,否则修改 sync.Map 已有的键值对。

7) Delete(key interface{}) 方法删除 sync.Map 的键值对,参数 key 是需要删除的键,如果参数 key 不在 sync.Map 中,则程序不执行任何操作。

8) LoadAndDelete(key interface{}) (value interface{}, loaded bool) 方法从 sync.Map 查找某个键值对,然后删除该键值对。参数 key 是需要删除的键,返回值 value 是键对应的值,返回值 loaded 是查找结果。如果参数 key 不在 sync.Map 中,返回值 value 为空值,返回值 loaded 为 false;若参数 key 在 sync.Map 中,返回值 value 为键对应的值,返回值 loaded 为 true。

9) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) 方法从 sync.Map 读取键值对或新增键值对。参数 key 是读取或新增的键;参数 value 是键对应的值,如果键值对不在 sync.Map 中,则执行新增操作,否则执行读取操作;返回值 actual 是查找键值对的数据;返回值 loaded 是查找结果。若参数 key 在 sync.Map 中,则返回 true,否则返回 false。

10) Range(f func(key, value interface{}) bool) 方法遍历 sync.Map 所有的键值对。参数 f 是匿名函数,每次遍历结果都会通过匿名函数返回,匿名函数的参数 key 和 value 代表键值对,bool 代表该方法返回值的数据类型。

根据 sync.Map 的语法定义,我们通过简单的例子说明如何使用 sync.Map,示例如下:
package main

import (
   "fmt"
   "sync"
)

func main() {
   // 定义sync.Map类型的变量m
   var m sync.Map

   // Store()写入数据
   m.Store("name", "Tom")
   m.Store("age", 10)
   m.Store("address", "beijing")
   m.Store("vocation", "student")

   // Load()读取数据
   name, _ := m.Load("name")
   fmt.Printf("sync.Map的name数据:%v\n", name)
   age, _ := m.Load("age")
   fmt.Printf("sync.Map的age数据:%v\n", age)

   // Delete()删除数据
   m.Delete("address")
   fmt.Printf("sync.Map的数据:%v\n", m)

   // LoadAndDelete()读取并删除数据
   vocation, ok := m.LoadAndDelete("vocation")
   if ok{
        // 读取成功后输出数据
        fmt.Printf("sync.Map的vocation数据:%v\n", vocation)
        // 查看读取成功后是否删除数据
        fmt.Printf("sync.Map的数据:%v\n", m)
   }

   // LoadOrStore()读取或新增数据
   // 新增数据,如果key不存在,将参数key和value作为新的键值对写入
   live, ok := m.LoadOrStore("live", "BJ")
   fmt.Printf("sync.Map新增live数据:%v\n", live)
   fmt.Printf("sync.Map的数据:%v\n", m)
   // 读取数据,如果key已存在,直接获取已有的value,参数value的值不起作用
   ages, ok := m.LoadOrStore("age", 15)
   fmt.Printf("sync.Map读取age数据:%v\n", ages)

   // 遍历输出数据
   m.Range(func(key, value interface{}) bool {
        fmt.Printf("sync.Map的key:%v\n", key)
        fmt.Printf("sync.Map的value:%v\n", value)
        return true
   })
}
运行上述代码,运行结果为:
sync.Map的name数据:Tom
sync.Map的age数据:10
sync.Map的数据:{ {0 0} {map[age:0xc000006030 name:0xc000006028 vocation:0xc000006040] false}} map[] 0}
sync.Map的vocation数据:student
sync.Map的数据:{{0 0} {{map[age:0xc000006030 name:0xc000006028 vocation:0xc000006040] false}} map[] 0}
sync.Map新增live数据:BJ
sync.Map的数据:{{0 0} {{map[age:0xc000006030 live:0xc000006050 name:0xc000006028 vocation:0xc000006040] true}} map[age:0xc000006030 live:0xc000006050 name:0xc000006028] 0}
sync.Map读取age数据:10
sync.Map的key:name
sync.Map的value:Tom
sync.Map的key:age
sync.Map的value:10
sync.Map的key:live
sync.Map的value:BJ

下一步讲述如何在并发中使用 sync.Map,示例如下:
package main

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

// 定义全局变量
// 定义同步等待组
var wg sync.WaitGroup

// 定义并发函数
func set_amap(m *sync.Map, b int) {
   // 参数m以指针接收者方式表示
   for i := 1; i < 5; i++ {
        m.Store("age", i+b)
        v, _ := m.Load("age")
        fmt.Printf("sync.Map的age数据:%v\n", v)
   }
   // 释放同步等待
   wg.Done()
}

func main() {
   // 记录程序开始时间
   start := time.Now()
   // 设置同步等待组
   wg.Add(2)
   var m sync.Map
   // 执行并发操作,sync.Map以指针形式作为参数传递
   go set_amap(&m, 0)
   go set_amap(&m, 10)
   // 等待同步等待组
   wg.Wait()
   // 记录程序结束时间并计算执行时间
   end := time.Now()
   consume := end.Sub(start).Seconds()
   fmt.Println("程序执行耗时(s):", consume)
}
运行上述代码,运行结果为:

sync.Map的age数据:11
sync.Map的age数据:12
sync.Map的age数据:13
sync.Map的age数据:14
sync.Map的age数据:1
sync.Map的age数据:2
sync.Map的age数据:3
sync.Map的age数据:4
程序执行耗时(s):0.0011221

与文章开头程序的输出结果对比后不难发现,使用 sync.Map 在执行效率上更为高效,并且在使用上无须自行加锁处理,示例说明如下:

1) 定义全局变量 wg,数据类型为 sync.WaitGroup,它为并发程序设置同步等待功能。

2) 定义并发函数 set_amap(),参数 m 是指针类型的 sync.Map,参数通过指针接收者方式传递 sync.Map 变量;参数 b 是整型数据,用来设置 sync.Map 的 age 数据。

3) 主函数 main() 首先创建时间变量 start 和设置同步等待组的并发数量,然后定义 sync.Map 变量 m 和执行函数 set_amap() 的并发操作,将变量 m 以指针方式作为函数参数,最后设置变量 wg 的等待状态和计算程序执行时间。

4) sync.Map 作为函数参数的时候,参数类型建议使用指针接收者表示,如果参数改用值接收者也能执行,但 GoLand 中会提示警告信息,如下图所示。


图:警告信息

相关文章