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