golang并发编程
golang并发编程
引子
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 = 0
} 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(3)
for i:=0;i<10;i++{
wg.Add(1)
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(1000*1000/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(3)
b:=time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
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(20)
//使用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{}
如果您有更好的方案欢迎交流与分享。
golang并发编程的更多相关文章
- Golang - 并发编程
目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...
- golang并发编程goroutine+channel(一)
go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致.go语言的并发关键词 "go" go dos ...
- golang并发编程的两种限速方法
引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...
- Golang并发编程基础
硬件 内存 作为并发编程一个基础硬件知识储备,首先要说的就是内存了,总的来说在绝大多数情况下把内存的并发增删改查模型搞清楚了其他的基本上也是异曲同工之妙. 内存芯片--即我们所知道的内存颗粒,是一堆M ...
- Golang并发编程优势与核心goroutine及注意细节
Go语言为并发编程而内置的上层API基于CSP(communication sequential processes,顺序通信进程)模型.这就意味着显式锁都是可以避免的,比如资源竞争,比如多个进程同时 ...
- Golang并发编程进程通信channel了解及简单使用
概念及作用 channel是一个数据类型,用于实现同步,用于两个协程之间交换数据.goroutine奉行通过通信来共享内存,而不是共享内存来通信.引用类型channel是CSP模式的具体实现,用于多个 ...
- Golang并发编程——goroutine、channel、sync
并发与并行 并发和并行是有区别的,并发不等于并行. 并发 两个或多个事件在同一时间不同时间间隔发生.对应在Go中,就是指多个 goroutine 在单个CPU上的交替运行. 并行 两个或者多个事件在同 ...
- Golang并发编程中select简单了解
select可以监听channel的数据流动select的用法与switch语法非常类似,由select开始的一个新的选择块,每个选择条件由case语句来描述 与switch语句可以选择任何使用相等比 ...
- Golang并发编程有缓冲通道和无缓冲通道(channel)
无缓冲通道 是指在接收前没有能力保存任何值得通道.这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作.如果两个goroutine没有同时准备好,通道会导 ...
随机推荐
- Android Gson深入分析
眼下解析json有三种工具:org.json(Java经常使用的解析),fastjson(阿里巴巴project师开发的).Gson(Google官网出的).解析速度最快的是Gson,下载地址:htt ...
- 使用my97datepicker控件实现日期范围选择
注:(2014-12-05内容修改:添加运行效果) 使用my97datepicker 控件,需要对日期的范围进行控制,本人自己写了一个js完成此功能,示例为当前日期到下一周周五之间的日期可选,其他日期 ...
- JS关闭页面无提示
window.opener=null; window.open('','_self'); window.close();
- samba服务器的安装及配置
安装前首先查看服务器是否已经安装samba服务器 [root@bogon home]# rpm -qa|grep samba system-config-samba-docs-1.0.9-1.el6. ...
- log4j.xml配置示例
这是log4j1.x版本讲解,log4j-1.2.16.jar 1. 一般的log4j.xml的两种配置方式: 1.Logger 完成日志信息的处理定义输出的层次和决定信息是否输出DEBUG&l ...
- 对于System.Net.Http的学习(一)——System.Net.Http 简介(转)
最新在学习System.Net.Http的知识,看到有篇文章写的十分详细,就想转过来,自己记录下.原地址是http://www.cnblogs.com/chillsrc/p/3439215.html? ...
- VS2013无法链接到TFS (转)
VS2013无法链接到TFS(Visual studio online),错误TF31001,TF31002 TF31002: Unable to connect to VisualStudio ...
- MySQL 基础学习
http://www.w3school.com.cn/sql/ 1.limit x,y 或 limit z :选取从x开始的y条数据 或 选取最开始的 z条数据 , 2.like '%N%' : ...
- python爬虫数据抓取方法汇总
概要:利用python进行web数据抓取方法和实现. 1.python进行网页数据抓取有两种方式:一种是直接依据url链接来拼接使用get方法得到内容,一种是构建post请求改变对应参数来获得web返 ...
- IOS 特定于设备的开发:获取和使用设备姿势(通过手机方向控制3d物体显示)
利用设备的机载陀螺仪可以实现,当你旋转手机屏幕时,里面的画面不会随着视图更新而移动,以平衡物理运动. 下面例子利用少量简单的几何变换执行该操作.他建立一个运动管理器,订阅设备运动更新,然后基于运动管理 ...