map
线程不安全
Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
下面来看下并发情况下读写 map 时会出现的问题,代码如下:
package main
import "fmt"
func main() {
m := make(map[int]int)
go func() {
// 不停地对map进行写入
for {
m[1] = 1
}
}()
go func() {
// 不停地对map进行读取
for {
_ = m[1]
}
}()
fmt.Scanln()
}
无法运行,报错:
fatal error: concurrent map read and map write
错误信息显示:并发的map读和map写,也就是说使用了两个并发函数不断的对map进行读和写而发生了竞态问题,map 内部会对这种并发操作进行检查并提前发现。
需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
解决方法
加锁
package main
import (
"fmt"
"sync"
"time"
)
type SyncMap struct {
mymap map[string]string
//读写锁包含一对相关的锁,读锁用于只读操作,写锁用于写操作。读锁可能由多个读线程同时运行,写锁是唯一的。
// * 读锁和写锁之间是互斥的,同一时间只能有一个在运行。但是可以有多个线程同时读取数据。
// * 写入数据之前必须重新确认(ReCheck)状态,因为其他的线程可能会拿到写锁再一次修改我们已经修改过的值。这是因为前一个线程拿到写锁之后,后面的线程会被阻塞。当前一个线程释放写锁之后,被阻塞的线程会继续运行完成被阻塞的部分代码,所以才会出现这样的情况。
// * 当某一个线程上了写锁之后,自己仍然可以上读锁,之后在释放写锁,这是一种降级(Downgrade)的处理方法。
*sync.RWMutex //读写锁
}
var smap SyncMap //公有的访问map
var done chan bool//通道,是否完成
func main() {
smap = SyncMap{map[string]string{}, new(sync.RWMutex)}
done = make(chan bool, 1000)
go func() {
for {
smap.Lock()
smap.mymap["1"] = "1";
smap.Unlock()
done<-true
time.Sleep(1*time.Millisecond)
}
}()
go func() {
for {
smap.Lock()
smap.mymap["1"] = "2";
smap.Unlock()
done<-true
time.Sleep(1*time.Millisecond)
}
}()
var lastlength = 0
var lock sync.Mutex
go func() {
for {
if len(done) != lastlength {
lock.Lock()
lastlength = len(done)
lock.Unlock()
smap.RLock()
fmt.Print(smap.mymap["1"] , "\t")
if len(done) % 5 ==0{
fmt.Println(" " , lastlength)
}
smap.RUnlock()
}
}
}()
for {
if len(done)==1000{
fmt.Println("通道已经满了")
break
}else{
time.Sleep(1*time.Second)
}
}
}
sync.Map
type Map struct
func (m *Map) Store(key, value interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Delete(key interface{})
sync.Map 有以下特性:
- 无须初始化,直接声明即可。
- sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
- 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
- LoadOrStore:参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true;不存在则store,返回该value 和false
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
m.Store("bb", 22)
m.Store("cc", 33)
m.Store("aa", 11)
m.Store("dd", 33)
m.Store("ee", 11)
//Load 方法,获得value
if v, ok := m.Load("cc");ok{
fmt.Printf("Load 方法,获得value %v: %v\n", v, ok)
}
m.Delete("cc")
//LoadOrStore方法,获取或者保存
// 就是如果key还在,那么就保持原来并返回原来的值,如果key不存在就存储
if vv,ok:=m.LoadOrStore("bb", 22);ok{
fmt.Println(vv)
}else{
fmt.Printf("LoadOrStore 方法,获得value %v: %v\n", vv, ok)
}
//遍历该map
m.Range(func(key, value interface{}) bool {
fmt.Printf("[%v]=[%v]\n", key, value)
return true
})
}
sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。