golang:线程安全的map----sync.Map

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 会有更好的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值