• 一道Gloang并发、锁的面试题,你会吗?


    1.题目描述

    源地址:一道并发和锁的golang面试题

    场景:
    在一个高并发的web服务器中,要限制IP的频繁访问。
    现模拟100个IP同时并发访问服务器,每个IP要重复访问1000次。
    每个IP三分钟之内只能访问一次。
    修改以下代码完成该过程,要求能成功输出 success:100

    package main
     
    import (
    	"fmt"
    	"time"
    )
     
    type Ban struct {
    	visitIPs map[string]time.Time
    }
     
    func NewBan() *Ban {
    	return &Ban{visitIPs: make(map[string]time.Time)}
    }
    func (o *Ban) visit(ip string) bool {
    	if _, ok := o.visitIPs[ip]; ok {
    		return true
    	}
    	o.visitIPs[ip] = time.Now()
    	return false
    }
    func main() {
    	success := 0
    	ban := NewBan()
    	for i := 0; i < 1000; i++ {
    		for j := 0; j < 100; j++ {
    			go func() {
    				ip := fmt.Sprintf("192.168.1.%d", j)
    				if !ban.visit(ip) {
    					success++
    				}
    			}()
    		}
     
    	}
    	fmt.Println("success:", success)
    }
    

    2.问题分析

    如果直接运行上述代码的话,会报错。

    2.1问题一

    变量j,表示的ip地址的最后一位,被多个协程使用,然而并不是以值的方式传入的。这种方式相当于拿着j的地址运行。看下面代码:

    for j := 0; j < 10; j++ {
    	go func() {
    		fmt.Println(j)
    	}()
    }
    

    打印结果,肯定不是0 1 2 3 4 …9

    2.2问题二

    变量success,被多个协程同时读写,且并未加锁

    2.3问题三

    visit函数中的map,也不是协程安全的

    2.4问题四

    启动了多个协程,并未等待这些协程,就有可能退出主main

    2.5问题五

    并未判断是否超时3s,假设时间超过了3s,success也不会加一

    3.问题解决方法

    ① 给闭包添加形参,将j作为实参传入
    ② success前后加锁,或者原子操作
    ③ map操作前后加锁,或者sync.map
    ④ 使用sync.WaitGroup对协程进行阻塞控制
    ⑤ 添加时间判断,超过3s的,success++,并重新更新时间

    4.代码实现

    go run -race *.go
    检测是否存在数据读写竞争

    4.1 map前后加锁的方式

    visit这样写可能不是最优,但是最容易理解

    package main
    
    import (
    	"fmt"
    	"sync"
    	"sync/atomic"
    	"time"
    )
    
    type Ban struct {
    	visitIPs map[string]time.Time
    }
    
    var mx sync.Mutex
    
    func NewBan() *Ban {
    	return &Ban{visitIPs: make(map[string]time.Time)}
    }
    func (o *Ban) visit(ip string) bool {  
    	mx.Lock()
    	defer mx.Unlock()
    	if t, ok := o.visitIPs[ip]; ok { //查看该ip是否已经访问过
    		if time.Now().Before(t.Add(3 * time.Second)) { //时间检测,查看当前时间是否在计时开始的3s后
    			//true 代表尚未到达3s
    			return true
    		} else {
    			//false 代表已经超过3s,则可以访问,并更新时间
    			o.visitIPs[ip] = time.Now()
    			return false
    		}
    	}
    	o.visitIPs[ip] = time.Now()
    	return false
    }
    
    func main() {
    	var success int64
    	var wg sync.WaitGroup
    	ban := NewBan()
    	for i := 0; i < 1000; i++ {
    		for j := 0; j < 100; j++ {
    			wg.Add(1)
    			go func(temp int) {
    				ip := fmt.Sprintf("192.168.1.%d", temp)
    				if !ban.visit(ip) {
    					atomic.AddInt64(&success, 1)
    				}
    				wg.Done()
    			}(j)
    		}
    
    	}
    	wg.Wait()
    	fmt.Println("success:", success)
    }
    

    4.2 sync.map解决方式

    只需要更改以下函数

    type Ban struct {
    	visitIPs sync.Map
    }
    
    func NewBan() *Ban {
    	return &Ban{visitIPs: sync.Map{}}
    }
    
    func (o *Ban) visit(ip string) bool {
    	mx.Lock()
    	defer mx.Unlock()
    	if v, ok := o.visitIPs.Load(ip); ok {
    		t := v.(time.Time)
    		if time.Now().Before(t.Add(3 * time.Second)) { //时间检测,查看当前时间是否在计时开始的3s后
    			//true 代表尚未到达3s
    			return true
    		} else {
    			//false 代表已经超过3s,则可以访问,并更新时间
    			o.visitIPs.Store(ip, time.Now())
    			return false
    		}
    	}
    	o.visitIPs.Store(ip, time.Now())
    	return false
    }
    
  • 相关阅读:
    win7 64位安装vs code
    iptables之SNAT,DNAT原理与DNS分离解析实验
    【git 介绍】AhuntSun
    深入理解Linux网络技术内 幕(六)——PCI层和网络接口卡
    【Python】jupyter lab虚拟环境选择错误
    Unity如何实现TreeView
    Java 数据结构与算法 堆
    深度解析ArrayList使用
    【JavaScript】参考手册-Array对象的3个属性和25个方法
    Jetpack的ViewModel与LiveData
  • 原文地址:https://blog.csdn.net/qq_26039331/article/details/127100993