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没有同时准备好,通道会导 ...
随机推荐
- JS学习笔记(二)运算符和流程控制语句
js中的运算符和流程控制,循环,判断语句都和C#基本一致,但又有其独特的运算符. typeof运算符 获得数据的数据类型,如number,string等.语法: string typeof(变量); ...
- VS快捷方式小技巧
VS2005代码编辑器的展开和折叠代码确实很方便和实用.以下是展开代码和折叠代码所用到的快捷键,很常用: Ctrl + M + O: 折叠所有方法 Ctrl + M + M: 折叠或者展开当前方法 C ...
- 读数据库所有表和表结构的sql语句
SQL获取所有数据库名.表名.储存过程以及参数列表 1.获取所有用户名:SELECT name FROM Sysusers where status='2' and islogin='1'islogi ...
- 制作OB图的时候,OB玩家进入后就退出的问题
开始怀疑是 OB玩家没有建筑 所以强行退出了,有朋友说那是因为有无效的触发造成的 我没有测试过 最后解决是给OB玩家在地图中加上建筑 更新 最后测试,把OB玩家放到一个组里 开局KILL掉这个组的建筑 ...
- latex如何把目录页的页码去掉?
页眉的显示与关闭,清空,还有样式之间的切换,需要用到如下几个命令: \pagestyle 用于设置当前页以及后续页面的页眉显示情况(可称为页版式).中间页版式可由\thispagestyle命令来指 ...
- 字符串-06. IP地址转换
/* * Main.c * D6-字符串-06. IP地址转换 * Created on: 2014年8月19日 *******测试通过******** *转载:http://blog.csdn.ne ...
- linux 使用者管理
1.用户标识符 UID 用户ID GID 用户组ID 2./etc/passwd 文件结构 id范围:0系统管理员|1~499 (系统账号)|500~65535 可登录账号
- IIS启动网站
在启动一个网站前要先打开一个服务.可是Windows 的提示非常的奇怪: “除非 Windows Activation Service (WAS)和万维网发布服务(W3SVC)均处于运行状态,否则无法 ...
- QT显示如何减轻闪屏(双缓冲和NoErase)
很多同志在些QT 程序后会遇见闪屏的问题, 有时速度非常快,但毕竟影响了显示效果,如何做到减轻屏幕抖动或闪屏呢?我曾试过如下的办法:1.使用双缓冲. 比如我们在一个Widget里面绘多个图的话, 先创 ...
- [置顶] SpecDD(混合的敏捷方法模型)主要过程概述
敏捷已成为当今使用最广泛的开发方法.有趣的是,敏捷方法的流行性并不是因为它取代了其他开发方法,相反它与这些方法进行了更好地融合.现实世界众多敏捷项目的成功,也证明了敏捷将走向杂化的未来. SpecDD ...