golang入门学习笔记(三)

作者: 一字马胡 | 来源:发表于2017-11-24 00:03 被阅读172次

作者: 一字马胡
转载标志 【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的思路,日后可以借鉴一下。

文/一字马胡

相关文章

网友评论

    本文标题:golang入门学习笔记(三)

    本文链接:https://www.haomeiwen.com/subject/kqatbxtx.html