## 有并发问题的map
官方的faq已经提到内检的map不是线程(goroutune)安全的.
首先,让我们看一段并发读写的代码,下列程序中一个goroutine一直读,一个goroutine一只写同一个键值,即即使读写的键不相同,而且map也没有"扩容"等操作,代码还是会报错。
```go
package main
func main() {
m := make(map[int]int)
go func() {
for {
_ = m[1]
}
}()
go func() {
for {
m[2] = 2
}
}()
select {}
}
```
错误信息是: fatal error: concurrent map read and map write。
如果你查看Go的源代码: hashmap_fast.go#L118,会看到读的时候会检查hashWriting标志, 如果有这个标志,就会报并发错误。
## sync.Map
特性:
1 空间换时间, 通过荣誉的两个数据结构(read, dirty), 实现加锁对性能的影响.
2 使用只读数据, 避免读写冲突
3 动态调整 miss次数多了以后, dirty数据提升为read
4 double checking
5 延迟删除, 删除只是打标记, 只有在提升dirty的时候才清除删除的数据
6 优先从read读取 更新 删除
7 虽然read 和dirty有冗余数据, 但这些书通过指针指向同一个数据. 所以空间有限.
//该 Map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。
//多个 goroutines 协程同时调用 Map 方法也是线程安全的。该 Map 的零值是有效的,
//并且零值是一个空的 Map 。线程安全的 Map 在第一次使用之后,不允许被拷贝
## sync.map range 方法
因为for ... range map是内建的语言特性,所以没有办法使用for range遍历sync.Map, 但是可以使用它的Range方法,通过回调的方式遍历。
```go
func (m *Map) Range(f func(key, value interface{}) bool) {
read, _ := m.read.Load().(readOnly)
// 如果m.dirty中有新数据,则提升m.dirty,然后在遍历
if read.amended {
//提升m.dirty
m.mu.Lock()
read, _ = m.read.Load().(readOnly) //双检查
if read.amended {
read = readOnly{m: m.dirty}
m.read.Store(read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
// 遍历, for range是安全的
for k, e := range read.m {
v, ok := e.load()
if !ok {
continue
}
if !f(k, v) {
break
}
}
}
```
- 命令行库cobra
- 用户路径检测go-homedir
- 配置解决方案viper(cobra配置用)
- 高效结构化日志库zap
- RPC框架grpc
- mongdb操作mgo
- ORM库xorm
- GRPCrest接口grpcgateway
- 使用gogoproto时grpcgateway的protobuf和json转换方法
- sync.Map
- zmq
- gogoproto
- go类型转换和类型断言
- go select用法详解以及定时器
- go并发资源竞争
- 官方命令行库flag
- 配置文件解析器 robig/config
- interface {} 接口
- goroutine && channel
- go 命名
- 类型switch
- 数据
- 初始化
- 指针方法 && 值方法
- 内嵌
- mqtt go实现
- grpc middleware