Go语言sync.Map的用法(非常详细)
在 Go 语言 1.6 版本之前,集合Map在并发程序中支持数据读取,但在写入过程中会存在异常,在 1.6 版本之后,通过并发读写集合 Map 都会提示异常。因此在 1.9 版本之前都是通过加锁处理或者封装成一个新的结构体,具体示例如下:
在 1.9 版本之后,Go 语言提供了一种效率较高且支持并发的数据类型,叫做 sync.Map。它是以结构体方式定义的,设有 4 个结构体成员和 8 个结构体方法,源码为:
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,示例如下:
下一步讲述如何在并发中使用 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 中会提示警告信息,如下图所示。

图:警告信息
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
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 中会提示警告信息,如下图所示。

图:警告信息