首页 文章资讯内容详情

golang并发编程的两种限速方法

2026-06-01 4 花语

本文内容纲要:

-引子 -方案一 -实现 -测试 -方案二 -实现 -测试 -改造

引子

golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU、内存、带宽等),我们就需要对程序限速,以防止goroutine将资源耗尽。

以下面伪代码为例,看看goroutine如何拖垮一台DB。假设userList长度为10000,先从数据库中查询userList中的user是否在数据库中存在,存在则忽略,不存在则创建。 //不使用goroutine,程序运行时间长,但数据库压力不大 for_,v:=rangeuserList{ user:=db.user.Get(v.ID) ifuser==nil{ newUser:=user{ID:v.ID,UserName:v.UserName} db.user.Insert(newUser) } } //使用goroutine,程序运行时间短,但数据库可能被拖垮 for_,v:=rangeuserList{ u:=v gofunc(){ user:=db.user.Get(u.ID) ifuser==nil{ newUser:=user{ID:u.ID,UserName:u.UserName} db.user.Insert(newUser) } }() } select{}

在示例中,DB在1秒内接收10000次读操作,最大还会接受10000次写操作,普通的DB服务器很难支撑。针对DB,可以在连接池上做手脚,控制访问DB的速度,这里我们讨论两种通用的方法。

方案一

在限速时,一种方案是丢弃请求,即请求速度太快时,对后进入的请求直接抛弃。

实现

实现逻辑如下:

packagemain import( "sync" "time" ) //LimitRate限速 typeLimitRatestruct{ rateint begintime.Time countint locksync.Mutex } //LimitLimit func(l*LimitRate)Limit()bool{ result:=true l.lock.Lock() //达到每秒速率限制数量,检测记数时间是否大于1秒 //大于则速率在允许范围内,开始重新记数,返回true //小于,则返回false,记数不变 ifl.count==l.rate{ iftime.Now().Sub(l.begin)>=time.Second{ //速度允许范围内,开始重新记数 l.begin=time.Now() l.count=0 }else{ result=false } }else{ //没有达到速率限制数量,记数加1 l.count++ } l.lock.Unlock() returnresult } //SetRate设置每秒允许的请求数 func(l*LimitRate)SetRate(rint){ l.rate=r l.begin=time.Now() } //GetRate获取每秒允许的请求数 func(l*LimitRate)GetRate()int{ returnl.rate }

测试

下面是测试代码:

packagemain import( "fmt" ) funcmain(){ varwgsync.WaitGroup varlrLimitRate lr.SetRate(3) fori:=0;i<10;i++{ wg.Add(1) gofunc(){ iflr.Limit(){ fmt.Println("Gotit!")//显示3次Gotit! } wg.Done() }() } wg.Wait() }

运行结果

Gotit! Gotit! Gotit!

只显示3次Gotit!,说明另外7次Limit返回的结果为false。限速成功。

方案二

在限速时,另一种方案是等待,即请求速度太快时,后到达的请求等待前面的请求完成后才能运行。这种方案类似一个队列。

实现

//LimitRate限速 typeLimitRatestruct{ rateint intervaltime.Duration lastActiontime.Time locksync.Mutex } //Limit限速 packagemain import( "sync" "time" ) func(l*LimitRate)Limit()bool{ result:=false for{ l.lock.Lock() //判断最后一次执行的时间与当前的时间间隔是否大于限速速率 iftime.Now().Sub(l.lastAction)>l.interval{ l.lastAction=time.Now() result=true } l.lock.Unlock() ifresult{ returnresult } time.Sleep(l.interval) } } //SetRate设置Rate func(l*LimitRate)SetRate(rint){ l.rate=r l.interval=time.Microsecond*time.Duration(1000*1000/l.Rate) } //GetRate获取Rate func(l*LimitRate)GetRate()int{ returnl.rate }

测试

packagemain import( "fmt" "sync" "time" ) funcmain(){ varwgsync.WaitGroup varlrLimitRate lr.SetRate(3) b:=time.Now() fori:=0;i<10;i++{ wg.Add(1) gofunc(){ iflr.Limit(){ fmt.Println("Gotit!") } wg.Done() }() } wg.Wait() fmt.Println(time.Since(b)) }

运行结果

Gotit! Gotit! Gotit! Gotit! Gotit! Gotit! Gotit! Gotit! Gotit! Gotit! 3.004961704s

与方案一不同,显示了10次Gotit!但是运行时间是3.00496秒,同样每秒没有超过3次。限速成功。

改造

回到最初的例子中,我们将限速功能加进去。这里需要注意,我们的例子中,请求是不能被丢弃的,只能排队等待,所以我们使用方案二的限速方法。

varlrLimitRate//方案二 //限制每秒运行20次,可以根据实际环境调整限速设置,或者由程序动态调整。 lr.SetRate(20) //使用goroutine,程序运行时间短,但数据库可能被拖垮 for_,v:=rangeuserList{ u:=v gofunc(){ lr.Limit() user:=db.user.Get(u.ID) ifuser==nil{ newUser:=user{ID:u.ID,UserName:u.UserName} db.user.Insert(newUser) } }() } select{}

如果您有更好的方案欢迎交流与分享。

内容为作者原创,未经允许请勿转载,谢谢合作。

关于作者:

Jesse,目前在Joygenio工作,从事golang语言开发与架构设计。

正在开发维护的产品:www.botposter.com

本文内容总结:引子,方案一,实现,测试,方案二,实现,测试,改造,

原文链接:https://www.cnblogs.com/jesse-joygenio/p/goroutine-limie-rate.html