引子

golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU、内存、带宽等),我们就需要对程序限速,以防止goroutine将资源耗尽。以下面伪代码为例,看看goroutine如何拖垮一台DB。假设userList长度为10000,先从数据库中查询userList中的user是否在数据库中存在,存在则忽略,不存在则创建。

//不使用goroutine,程序运行时间长,但数据库压力不大
for _,v:=range userList {
user:=db.user.Get(v.ID)
if user==nil {
newUser:=user{ID:v.ID,UserName:v.UserName}
db.user.Insert(newUser)
}
} //使用goroutine,程序运行时间短,但数据库可能被拖垮
for _,v:=range userList {
u:=v
go func(){
user:=db.user.Get(u.ID)
if user==nil {
newUser:=user{ID:u.ID,UserName:u.UserName}
db.user.Insert(newUser)
}
}()
}
select{}

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

方案一

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

实现

实现逻辑如下:

package main

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

测试

下面是测试代码:

package main

import (
"fmt"
) func main() {
var wg sync.WaitGroup
var lr LimitRate
lr.SetRate() for i:=;i<;i++{
wg.Add()
go func(){
if lr.Limit() {
fmt.Println("Got it!")//显示3次Got it!
}
wg.Done()
}()
}
wg.Wait()
}

运行结果

Got it!
Got it!
Got it!

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

方案二

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

实现

//LimitRate 限速
type LimitRate struct {
rate int
interval time.Duration
lastAction time.Time
lock sync.Mutex
}
//Limit 限速
package main import (
"sync"
"time"
) func (l *LimitRate) Limit() bool {
result := false
for {
l.lock.Lock()
//判断最后一次执行的时间与当前的时间间隔是否大于限速速率
if time.Now().Sub(l.lastAction) > l.interval {
l.lastAction = time.Now()
result = true
}
l.lock.Unlock()
if result {
return result
}
time.Sleep(l.interval)
}
} //SetRate 设置Rate
func (l *LimitRate) SetRate(r int) {
l.rate = r
l.interval = time.Microsecond * time.Duration(*/l.Rate)
} //GetRate 获取Rate
func (l *LimitRate) GetRate() int {
return l.rate
}

测试

package main

import (
"fmt"
"sync"
"time"
) func main() {
var wg sync.WaitGroup
var lr LimitRate
lr.SetRate() b:=time.Now()
for i := ; i < ; i++ {
wg.Add()
go func() {
if lr.Limit() {
fmt.Println("Got it!")
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(time.Since(b))
}

运行结果

Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
3.004961704s

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

改造

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

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

Go -- 并发编程的两种限速方法的更多相关文章

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

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

  2. Perl 面向对象编程的两种实现和比较:

    <pre name="code" class="html">https://www.ibm.com/developerworks/cn/linux/ ...

  3. angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用

    今天我们要讲的是ng2的路由系统. 例子

  4. git两种合并方法 比较merge和rebase

    18:01 2015/11/18git两种合并方法 比较merge和rebase其实很简单,就是合并后每个commit提交的id记录的顺序而已注意:重要的是如果公司用了grrit,grrit不允许用m ...

  5. 两种Ajax方法

    两种Ajax方法 Ajax是一种用于快速创建动态网页的技术,他通过在后台与服务器进行少量的数据交换,可以实现网页的异步更新,不需要像传统网页那样重新加载页面也可以做到对网页的某部分作出更新,现在这项技 ...

  6. mysql in 的两种使用方法

    简述MySQL 的in 的两种使用方法: 他们各自是在 in keyword后跟一张表(记录集).以及在in后面加上字符串集. 先讲后面跟着一张表的. 首先阐述三张表的结构: s(sno,sname. ...

  7. C#中的两种debug方法

    这篇文章主要介绍了C#中的两种debug方法介绍,本文讲解了代码用 #if DEBUG 包裹.利用宏定义两种方法,需要的朋友可以参考下   第一种:需要把调试方法改成debug代码用 #if DEBU ...

  8. Service的两种启动方法

    刚才看到一个ppt,介绍service的两种启动方法以及两者之间的区别. startService 和 bindService startService被形容为我行我素,而bindService被形容 ...

  9. jQuery ajax调用后台aspx后台文件的两种常见方法(不是ashx)

    在asp.net webForm开发中,用Jquery ajax调用aspx页面的方法常用的有两种:下面我来简单介绍一下. [WebMethod] public static string SayHe ...

随机推荐

  1. PAT 乙级 1009

    题目 题目地址:PAT 乙级 1009 题解 本题本身属于比较简单的字符串操作题,但是因为对于string的操作和函数不熟悉导致本题做起来很费劲,需要加强对于string类以及相关方法的理解和熟练程度 ...

  2. redis集群理解

    Redis在3.0中也引入了集群的概念,用于解决一些大数据量和高可用的问题,但是,为了达到高性能的目的,集群不是强一致性的,使用的是异步复制,在数据到主节点后,主节点返回成功,数据被异步地复制给从节点 ...

  3. Git学习——删除文件

    怎么删除版本库中的文件呢? 首先,删除工作区中的文件:rm <file>,然后你有两个选择.其一是,真正删除版库中的文件: git rm <file> git commit - ...

  4. selenium.common.exceptions.WebDriverException: Message: u'unknown error: cannot get automation extension\nfrom unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfeb

    Python2.7 selenium3.4.1在使用chrome driver时报错:selenium.common.exceptions.WebDriverException: Message: u ...

  5. HashMap图解

    HashMap的数据结构和put.get.resize等操作的图解,看图轻松掌握HashMap (目前还不包括红黑树相关的部分) HashMap数据结构如下图 HashMap之put操作如下图 Has ...

  6. MIP经典问题:旅行商问题 (traveling salesman problem)

    *本文主要记录和分享学习到的知识,算不上原创. *参考文献见链接. 旅行商问题.背包问题都是0-1规划问题中最为经典的问题. 通常来说,当我们学习并熟悉一种求解混合整数问题的技巧时,可以用这种技巧来求 ...

  7. Knockout v3.4.0 中文版教程-8-计算监控-纯计算属性

    4.纯计算属性 纯计算监控在KO 3.2.0中开始引入,比大多数应用程序使计算监控有更大的性能提升和内存优化.这是因为在自身没有订阅的时候不会保持订阅状态.特性如下 阻止内存泄露 - 避免在应用程序里 ...

  8. Java-重定义标准输出和错误

    刚接触到错误日志的时候觉得很神奇,今天学习了一番后用代码写了出来. package com.tj; import java.io.File; import java.io.FileNotFoundEx ...

  9. [LoadRunner]LR性能测试结果样例分析

    R性能测试结果样例分析 测试结果分析 LoadRunner性能测试结果分析是个复杂的过程,通常可以从结果摘要.并发数.平均事务响应时间.每秒点击数.业务成功率.系统资源.网页细分图.Web服务器资源. ...

  10. Sort a linked list in O(n log n) time using constant space complexity.

    因为题目要求复杂度为O(nlogn),故可以考虑归并排序的思想. 归并排序的一般步骤为: 1)将待排序数组(链表)取中点并一分为二: 2)递归地对左半部分进行归并排序: 3)递归地对右半部分进行归并排 ...