Gloang并发、锁的面试题
- 1.题目描述
- 2.问题分析
- 2.1问题一
- 2.2问题二
- 2.3问题三
- 2.4问题四
- 2.5问题五
- 3.问题解决方法
- 4.代码实现
- 4.1 map前后加锁的方式
- 4.2 sync.map解决方式
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 {
if time.Now().Before(t.Add(3 * time.Second)) {
return true
} else {
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)) {
return true
} else {
o.visitIPs.Store(ip, time.Now())
return false
}
}
o.visitIPs.Store(ip, time.Now())
return false
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)