构造并发操作场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
func main() { m := make(map[int]int) go func() { for { _ = m[1] } }() go func() { for { m[0] = 1 } }() select {} }
|
go的map即使读写的不是一个建, 只要存在同时间访问就会出现并发读写的异常
解决方案
1. 使用读写锁
这里我们使用读写锁来保证Map的并发安全, 当然使用互斥锁也行, 但是当读操作多的时候性能会差一些,
因为读锁和读锁不互斥所以锁竞争相对小
操作要点
- 读的时候加读锁, 读完解锁
- 写的时候加写锁, 写完解锁
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package main
import "sync"
type MapWithLock struct { data map[int]int sync.RWMutex }
func NewMapWithLock() *MapWithLock { mapWithLock := new(MapWithLock) mapWithLock.data = make(map[int]int) return mapWithLock }
func (m *MapWithLock) Set(key, value int) { m.Lock() m.data[key] = value m.Unlock() }
func (m *MapWithLock) Get(key int) int { m.RLock() value := m.data[key] m.RUnlock() return value }
func main() { m := NewMapWithLock() go func() { for { _ = m.Get(1) } }() go func() { for { m.Set(0, 1) } }() select {} }
|
2. 使用channel
使用channel线程安全的特性来保证Map的操作是串行的
操作要点
- 无缓存的channel在写入的时候只能写入一个, 另一个写入操作会被阻塞
- 在创建map的时候我们启动一个Goroutine来读取channel里面的操作
- 在读的时候需要添加等待组, 不然可能会拿到空值 (原因是没等函数执行完, 就return了)
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package main
import ( "fmt" "sync" )
type MapWithChannel struct { data map[int]int ch chan func() }
func NewMapWithChannel() *MapWithChannel { mapWithChannel := new(MapWithChannel) mapWithChannel.data = make(map[int]int) mapWithChannel.ch = make(chan func()) mapWithChannel.readChannel() return mapWithChannel }
func (m *MapWithChannel) set(key, value int) { m.data[key] = value }
func (m *MapWithChannel) get(key int) int { value := m.data[key] return value }
func (m *MapWithChannel) Set(key, value int) { m.ch <- func() { m.set(key, value) } }
func (m *MapWithChannel) Get(key int) (value int) { wg := sync.WaitGroup{} wg.Add(1) m.ch <- func() { value = m.get(key) wg.Done() } wg.Wait() return value }
func (m *MapWithChannel) readChannel() { go func() { for { f := <-m.ch f() } }() }
func main() { m := NewMapWithChannel() go func() { for { x := m.Get(0) fmt.Println(x) } }() go func() { for { m.Set(0, 1) } }() select {} }
|
3. 使用sync.Map
sync.Map 是线程安全的, 可以大胆使用, 但是在写操作较多的时候性能不太好, 建议读操作多的时候使用,
写操作多的话还是老老实实加锁
操作要点
- sync.Map 的键和值都是interface{}类型的, 使用的时候需要进行类型断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package main
import ( "fmt" "sync" )
func main() { m := sync.Map{} go func() { for { v, ok := m.Load(0) if !ok { break } value, ok := v.(int) if !ok { break } fmt.Println(value) } }() go func() { for { m.Store(0, 1) } }() select {} }
|