map在高并发场景下如何安全使用

引言:在go语言中map是非线程安全的,也就是其底层代码是不带锁的,不带锁,在并发场景下就会出现安全问题。

1、为什么map不设置成并发安全的?

Go官方在经过了长时间的讨论后,认为Go map 更应适配典型使用场景(不需要从多个 goroutine 中进行安全访问),而不是为了小部分情况(并发访问),导致大部分程序付出加锁代价(性能),决定了不支持。
场景: 2个协程同时读和写,以下程序会出现致命错误:fatalerror:concurrent map writes

2、那么如果真的有并发访问的场景该如何处理?

1、使用sync.Mutex

既然他底层代码没有锁,那么我们就调用go语言的sync包下的Mutex给它单独上锁,每次要操作map的前后都加锁,这样就不会出现多个协程同时对map进行读写操作了

var Locker sync.Mutex
var clientMap = make(map[string]int)
func main() {
	for i := 0; i < 100; i++ {
		go func() {
			key := fmt.Sprintf("key%d", i)
			Locker.Lock()
			clientMap[key] = i
			Locker.Unlock()
		}()
	}

}

2、使用sync.Map{}

我们可以使用sync包下的一个sync.Map{}这个map是内部是有锁的,可以支持并发访问安全

func main() {
	mapTest := sync.Map{}
	success := 0
	for i := 0; i < 100; i++ {
		//h++
		go func() {
			key := fmt.Sprintf("key%d", i)
			mapTest.Store(key, "value")
			success++
		}()
	}
	time.Sleep(3 * time.Second)
	fmt.Println(success)
}

3、简单小场景测试

//场景:在一个高并发的web服务器中,要限制IP的频繁访问。
//以下代码模拟100个IP同时并发访问服务器,每个IP会重复访问1000次。限制每个IP三分钟之内只能访问一次。

每句代码的解析都放到注释里了,可以复制到goland上测试一下。

// Ban 使用map存储IP访问记录但是因为是高并发场景,普通的map是无法满足并发访问的,需要使用互斥锁来保证线程安全。
// 如果不用锁的话,一定会出现map在多个协程中同时读写,产生panic错误
type Ban struct {
	visitIPs map[string]time.Time
	mu       sync.Mutex // 使用互斥锁来保证线程安全
}

// NewBan 创建一个Ban对象
func NewBan() *Ban {
	return &Ban{
		visitIPs: make(map[string]time.Time),
		mu:       sync.Mutex{},
	}
}

// true 表示三分钟内访问过 限制访问,false表示三分钟内没有访问过
func (o *Ban) visit(ip string) bool {
	o.mu.Lock()
	defer o.mu.Unlock()

	// 检查当前时间与IP的最后访问时间之间的差异
	lastVisit, ok := o.visitIPs[ip]
	time.Since(lastVisit) < 3*time.Minute 计算从 lastVisit 到当前时间的持续时间
	if ok && time.Since(lastVisit) < 3*time.Minute {
		return true
	}
	// 更新IP的最后访问时间
	o.visitIPs[ip] = time.Now()
	return false
}

func main() {
	// 统计成功次数
	success := 0
	ban := NewBan()
	// 模拟1000次访问
	for j := 0; j < 1000; j++ {
		// 模拟100个IP同时并发访问服务器
		for i := 0; i < 100; i++ {
			ip := fmt.Sprintf("192.168.1.%d", i)
			go func(ip string) {
				if !ban.visit(ip) {
					success++ //操作的成功次数
					// 模拟耗时操作  在协程中的其他操作所耗费世界
					time.Sleep(time.Millisecond * 200)
				}
			}(ip)
		}
	}

	// 等待所有goroutine完成
	time.Sleep(3 * time.Second)
	fmt.Println("success:", success)
}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值