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

图:警告信息
ICP备案:
公安联网备案: