作者: 一字马胡
转载标志 【2017-11-23】
更新日志
日期 | 更新内容 | 备注 |
---|---|---|
2017-11-23 | 新建文章 | go语言入门学习笔记(三) |
golang入门学习笔记系列
golang入门学习笔记(一)
golang入门学习笔记(二)
Select for golang
Go’s select lets you wait on multiple channel
operations. Combining goroutines and channels
with select is a powerful feature of Go.
go中的select类似于Linux中的select I/O,事件驱动,select等待在多个Channel,当某个Channel的数据准备好了的时候就会执行相应的动作,select保证会阻塞等待在一组Channel上,直到某个Channel完成(包括default语句),下面是一个使用select的例子:
// delay "delay" s then push the msg to channel "ch"
// just test the select of goLang
// difference msg and delay is needed
func selectFunc(msg string, delay int, ch chan string) {
if delay == 0 {
delay = 1 // default delay 1s
} else if delay > 5 {
delay = 5 // the max delay time
}
time.Sleep(time.Second * time.Duration(delay))
if msg == "" {
ch <- "default msg from selectFunc";
} else {
ch <- msg
}
}
ch1 := make(chan string)
ch2 := make(chan string)
go selectFunc("msg from channel 1", 1, ch1)
go selectFunc("msg from channel 2", 2, ch2)
//at least revive 2 msg
//
for i := 0; i < 2; i ++ {
select {
case msg := <- ch1:
log.Printf("%s\n", msg)
case msg := <- ch2:
log.Printf("%s\n", msg)
}
}
上面的例子展示了go语言中select的使用方法,它用于等待Channel的数据准备完成,当有一个被select监听的Channel完成I/O之后就会进行相应的操作,上面的例子中没有涉及default,default代表的意思是如果没有Channel完成了I/O,那么就默认执行default分支。上面的例子好像比较无聊,下面一个例子使用select来实现timeout的功能:
// using a goroutine to run this function.
// you can set the timeout value, then the function
// will wait some time, then set the channel to true
// means timeout
//
func timeoutFunc(timeout int, flag chan bool) {
if timeout == 0 {
flag <- true // timeout now.
}
time.Sleep(time.Second * time.Duration(timeout))
flag <- true
}
ch1 := make(chan string)
ch2 := make(chan string)
timeoutCh := make(chan bool)
go selectFunc("msg from channel 1", 1, ch1)
go selectFunc("msg from channel 2", 4, ch2)
go timeoutFunc(2, timeoutCh)
//at least revive 2 msg
//
for i := 0; i < 2; i ++ {
select {
case <- timeoutCh:
log.Printf("Time out !")
case msg := <- ch1:
log.Printf("%s\n", msg)
case msg := <- ch2:
log.Printf("%s\n", msg)
}
}
运行上面的代码,你将会发现输出一条msg之后就会输出超时了,这还是非常有趣并且有用的。有些时候,我们需要显示的关闭一个select,让它不再阻塞监听它所关联着的Channel,下面是一个使用close功能的select例子:
//check if we have more job to do.
//
func moreJobCheck(jobs chan int, done chan bool) {
for {
j , more := <- jobs
if more {
log.Printf("Receive job: %d\n", j)
} else {
done <- true
log.Printf("No more Jobs\n")
return
}
}
}
jobCh := make(chan int)
done := make(chan bool)
go moreJobCheck(jobCh, done)
for i := 0; i < 4; i ++ {
jobCh <- i
}
//close the job.
close(jobCh)
<- done
Timers for golang
We often want to execute Go code at some point in
the future, or repeatedly at some interval.
Go’s built-in timer and ticker features
make both of these tasks easy
Timers represent a single event in the future.
You tell the timer how long you want to wait,
and it provides a channel that will be notified at that time
If you just wanted to wait, you could have
used time.Sleep. One reason a timer may be
useful is that you can cancel the timer before it expires.
下面是关于timer的一个例子,上面提到,如果你仅仅是想要休眠一段时间,使用time.Sleep就可以了,但是使用timer的一个原因是你可以在timer超时之前取消它。
timeout1 := time.NewTimer(time.Second * time.Duration(1)) // timeout := 1s
timeout2 := time.NewTimer(time.Second * time.Duration(2)) // timeout := 2s
<- timeout1.C
log.Printf("timeout1 expired")
go func() {
<- timeout2.C
log.Printf("timeout2 expired")
}()
timeout2Stop := timeout2.Stop() // stop the timer
if timeout2Stop {
log.Printf("timeout2 was stoped")
}
tickers for golang
Timers are for when you want to do something
once in the future - tickers are for when
you want to do something repeatedly at regular intervals.
tickers和timer的区别在于,timer类似于计时器,会等待一段时间,而ticker类似于定时器,会周期性的超时,下面是一个使用ticker的例子:
ticker := time.NewTicker(time.Second * time.Duration(1)) // schedule 1s
go func() {
for t := range ticker.C {
log.Printf("Tocker at:%s\n", t)
}
} ()
time.Sleep(time.Second * time.Duration(2))
ticker.Stop()
Worker Pools
到目前为止已经学习了goroutine、Channel、select,那如何使用这些组件来实现Worker Pools呢?下面是一个实现:
//simulate the work,receive job ob the jobs channel,
// then do the job and get the result and send the
// corresponding results on results.
func wokrer(jobId int, jobs <- chan int, result chan <- int) {
for job := range jobs {
log.Printf("worker #%d start to do the job #%d", jobId, job)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
log.Printf("worker #%d finished to do the job #%d", jobId, job)
//corresponding results on results.
result <- job * jobId
}
}
jobs := make(chan int, 10)
result := make(chan int, 10)
for w := 1; w < 5; w ++ {
go wokrer(w, jobs, result)
}
for j := 1; j < 10; j ++ {
jobs <- j
}
close(jobs)
for r := 1; r < 5; r ++ {
<- result
}
上面的例子简单实现了一个worker pool,其实和java中的线程池是类似的,个中原委,还得自己慢慢体会啊!
Rate Limiting
Rate limiting is an important mechanism for controlling resource
utilization and maintaining quality of service. Go elegantly
supports rate limiting with >goroutines, channels, and tickers
下面是实现Rate Limiting的一个小例子:
mockRequest := make(chan int, 10)
for i := 1; i <= 10; i ++ {
mockRequest <- i
}
close(mockRequest)
limiter := time.Tick(time.Millisecond * time.Duration( 100 )) // 100 ms
for request := range mockRequest {
<- limiter
log.Printf("Request inCome:%d At %s", request, time.Now())
}
limiter的职责就是做限流,每过100ms再尝试去获取一个request来执行,这样就可以保护我们的server可以稳定运行了。但是这个例子中使用了timer来做Rate Limiting,下面的例子使用ticker来实现更复杂的Rate Limiting:
burstyLimiter := make(chan time.Time, 5)
for i := 0; i < 5; i ++ {
burstyLimiter <- time.Now()
}
go func() {
for t := range time.Tick(time.Millisecond * time.Duration(100)) {
burstyLimiter <- t
}
} ()
burstyRequest := make(chan int, 10)
for i := 1; i <= 10; i ++ {
burstyRequest <- i
}
close(burstyRequest)
for request := range burstyRequest {
<- burstyLimiter
log.Printf("Request inCome: %d At %s", request, time.Now())
}
上面的例子感觉就优点复杂了,我们使用了5个ticker来实现Rate Limiting,每个Rate Limiting过100ms接收一个请求来处理,当然Rate Limiting的需求是否那么紧迫还不得而知,我们总是希望我们的服务能跑得越快越好,QPS越高越好,但是同时我们也需要考虑是否需要做一些Rate Limiting的工作,这一点也需要仔细体会才能得到结论啊,但是golang提供了实现Rate Limiting的思路,日后可以借鉴一下。