定时器

1.定时器结构

  • 结构定义

    type Timer struct {
    C <-chan Time // 接受定时器事件的通道
    r runtimeTimer
    } type runtimeTimer struct {
    tb uintptr
    i int when int64
    period int64
    f func(interface{}, uintptr) // NOTE: must not be closure
    arg interface{}
    seq uintptr
    }

2.创建定时器

  • 接口定义

    func NewTimer(d Duration) *Timer
  • 使用简单实例

    var timer = NewTimer(time.Second)
    
    go func() {
    for {
    select {
    case <-timer.C:
    fmt.Println("time out.")
    }
    }
    }()
  • NewTimer源代码:

    func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1) // 创建一个带有一个Time结构缓冲的通道
    t := &Timer{
    C: c,
    r: runtimeTimer{ // 运行时定时器
    when: when(d), // 定时多久
    f: sendTime, // Golang写入时间的回调接口
    arg: c, // 往哪个通道写入时间
    },
    }
    startTimer(&t.r) // 启动提交定时器
    return t
    } // 时间到后,Golang自动调用sendTime接口,尝试往c通道写入时间
    func sendTime(c interface{}, seq uintptr) {
    // 给c通道以非阻塞方式发送时间
    // 如果被用于NewTimer, 无论如何不能阻塞.
    // 如果被用于NewTicker,接收方未及时接受时间,则会丢弃掉,因为发送时间是周期性的。
    select {
    case c.(chan Time) <- Now():
    default:
    }
    } func startTimer(*runtimeTimer)
  • 代码实例

    package main
    
    import (
    "fmt"
    "time"
    ) func main() { // 创建延迟3s的定时器
    exit := make(chan bool)
    timer := time.NewTimer(3 * time.Second) go func() {
    defer func() {
    exit <- true
    }() select {
    case <-timer.C:
    fmt.Println("time out.")
    return
    }
    }() <-exit
    }

3.停止定时器

  • 接口定义

    func (t *Timer) Stop() bool
    • 本接口可以防止计时器出触发。如果定时器停止,则返回true,如果定时器已过期或已停止,则返回false。

      Stop不关闭通道,以防止通道读取的操作不正确。

    • 为防止通过NewTimer创建的定时器,在调用Stop接口后触发,检查Stop返回值并清空通道。如:

      if !t.Stop() {
      <-t.C
      }

      但不能与Timer的通道中的其他接受同时进行!!!

    • 对于使用AfterFunc(d, f)创建的定时器,如果t.Stop()返回false,则定时器已经过期,并且函数f已经在自己的协程中启动。

      无论函数f是否执行完成,Stop()返回不会阻塞,如果用户需要知道f是否执行完毕,必须明确地与f协调。

  • 内部使用接口

    func stopTimer(*runtimeTimer) bool
  • 代码实例

    package main
    
    import (
    "fmt"
    "time"
    ) func main() {
    timer := time.NewTimer(time.Second)
    time.Sleep(time.Millisecond * 500)
    timer.Stop() fmt.Println("timer stopped")
    time.Sleep(time.Second * 3)
    }

4.重置定时器

  • 接口定义

    func (t *Timer) Reset(d Duration) bool
    • 定时器被激活,返回true,若定时器已过期或已被停止,返回false。

    • 若程序已从t.C中接受数据,定时器已知过期,t.Rest()可直接使用

    • 若程序还尚未从t.C收到一个值,则必须停止定时器。如果Stop提示计时器在停止之前已过期,则应明确清空通道。

      if !t.Stop() {
      <-t.c
      }
      t.Rest(d)
  • 代码实例

    package main
    
    import (
    "fmt"
    "time"
    ) func doTimer(t *time.Timer, exit chan<- bool) { go func(t *time.Timer) {
    defer func() {
    exit <- true
    }() for {
    select {
    case c := <-t.C:
    fmt.Println("timer timeout at", c)
    return
    }defer func() {
    ticker.Stop()
    fmt.Println("ticker stopped")
    } ()
    }
    }(t)
    } func main() {
    sign := make(chan bool)
    timer := time.NewTimer(time.Second * 3) doTimer(timer, sign)
    time.Sleep(time.Second) // 实际测试:注释下面三行代码,效果一样。
    if !timer.Stop() {
    <-timer.C
    } timer.Reset(time.Second * 3)
    fmt.Println("timer reset at", time.Now()) <-sign
    }

5.After接口

  • 接口定义

    func After(d Duration) <-chan Time
    • time.After函数,表示多少时间,写入当前时间,在取出channel时间之前不会阻塞,后续程序可以继续执行

    • time.After函数,通常用于处理程序超时问题

    • 等待一段时间d后,Golang会发送当前时间到返回的通道上。

    • 底层的定时器不会被GC回收,如果考虑效率,可使用NewTimer创建定时器,如果不需要,则调用Timer.Stop

  • 源码实例

    package main
    
    import (
    "fmt"
    "time"
    ) func main() {
    sign := make(chan bool)
    chan1 := make(chan int)
    chan2 := make(chan int) defer func() {
    close(sign)
    close(chan1)
    close(chan2)
    }() go func() {
    for {
    select {
    case c := <-time.After(time.Second * 3):
    fmt.Println("After at", c)
    // 若不往sign通道写入数据,程序循环每隔3s执行当前case分支。
    sign <- true
    case c1 := <-chan1:
    fmt.Println("c1", c1)
    case c2 := <-chan2:
    fmt.Println("c1", c2)
    }
    }
    }() <-sign
    }

6.AfterFun接口

  • 接口定义

    func AfterFunc(d Duration, f func()) *Timer
    • 等待一段时间d后,Golang会在自己的协程中调用f。并返回一个定时器,可以使用Stop方法取消调用
  • 代码实例

    package main
    
    import (
    "fmt"
    "time"
    ) func main() { timer := time.AfterFunc(time.Second*3, func() {
    fmt.Println("AfterFunc Callback")
    }) time.Sleep(time.Second * 5)
    timer.Stop()
    }

断续器

  • 断续器(滴答器)持有一个通道,该通道每隔一段时间发送时钟的滴答

  • 注1:从已经关闭的断续器中读取数据发生报错。所以在退出处理断续器流程前,需要先取消断续器。

  • 注2:经过取消的断续器,不能再复用,需要重新创建一个新的断续器。

  • 结构定义如下:

    type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    r runtimeTimer
    } type runtimeTimer struct {
    tb uintptr
    i int when int64
    period int64
    f func(interface{}, uintptr) // NOTE: must not be closure
    arg interface{}
    seq uintptr
    }
  • 初始化断续器

    var ticker = time.NewTicker(time.Second)
  • 取消断续器

    var ticker = time.NewTicker(time.Second)
    ticker.Stop()

实例一:使用Ticker(并使用时间控制ticker)

  • 代码如下:

    package main
    import (
    "fmt"
    "time"
    ) func TickerTest() *time.Ticker {
    // 创建一个断续器
    var ticker = time.NewTicker(time.Second) go func() {
    // 使用for + range组合处理断续器
    for t := range ticker.C {
    fmt.Println("tick at", t)
    }
    }() return ticker
    } func main() {
    ticker := TickerTest()
    time.Sleep(time.Second * 10)
    ticker.Stop()
    }

实例二:使用channel控制ticker

  • 参考链接:https://blog.csdn.net/yjp19871013/article/details/82048944

  • 代码如下:

    package main
    
    import (
    "fmt"
    "time"
    ) func DoTicker(ticker *time.Ticker) chan<- bool {
    stopChan := make(chan bool) go func(ticker *time.Ticker) {
    // 注册停止ticker方法
    defer ticker.Stop()
    for {
    select {
    // 处理断续器事件
    case t := <-ticker.C:
    fmt.Println("tick at", t)
    // 接受外部停止断续器事件
    case stop := <-stopChan:
    if stop {
    fmt.Println("DoTicker Exit")
    return
    }
    }
    }
    }(ticker) // 返回由外部控制Ticker停止的Channel
    return stopChan
    } func main() { var ticker = time.NewTicker(time.Second)
    stopChan := DoTicker(ticker) time.Sleep(time.Second * 10)
    // 停止断续器
    stopChan <- true
    time.Sleep(time.Second)
    close(stopChan)
    }

实例三:使用channel控制停止ticker

  • 参考链接:https://www.kancloud.cn/digest/batu-go/153534

  • 代码如下:

    package main
    
    import (
    "fmt"
    "time"
    ) func DoTicker(ticker *time.Ticker, times int) {
    // 创建有times个缓冲的byte通道
    stopChan := make(chan byte, times) go func(ticker *time.Ticker) { defer func() {
    // 经过调试,defer语句块并未执行
    ticker.Stop()
    fmt.Println("ticker stopped")
    } () for t := range ticker.C {
    fmt.Println("write stop channel") // 写满times次后,当前goroutine自动退出
    stopChan <- 0
    fmt.Println("tick at", t)
    } // 经调试,该语句并未执行
    fmt.Println("DoTicker1 Exit")
    }(ticker)
    } func main() {
    var ticker = time.NewTicker(time.Second) DoTicker(ticker, 5)
    time.Sleep(time.Second * 10)
    }
  • 调试输出:

    write stop channel
    tick at 2019-03-13 11:44:35.932692894 +0800 CST m=+1.000442776
    write stop channel
    tick at 2019-03-13 11:44:36.932643384 +0800 CST m=+2.000393270
    write stop channel
    tick at 2019-03-13 11:44:37.932565147 +0800 CST m=+3.000315031
    write stop channel
    tick at 2019-03-13 11:44:38.932735589 +0800 CST m=+4.000485469
    write stop channel
    tick at 2019-03-13 11:44:39.932553565 +0800 CST m=+5.000303443
    write stop channel Process finished with exit code 0

Golang定时器断续器的更多相关文章

  1. [Go] golang定时器与redis结合

    golang定时器与redis结合,每隔1秒ping一下,每隔20秒llen一下队列的长度 package main import ( "fmt" "time" ...

  2. [Go] golang定时器的使用

    golang中的定时器是使用的chanel阻塞来实现的,主要使用到了time包中的内容,如果有多个定时器的channel,为了防止阻塞,可以使用select来获取遍历channel 定时器获取的cha ...

  3. JS 定时器/延时器

    定时器 创建定时器    window.setInterval(方法类型,间隔时间(1000=1秒)) var timer=window.setInterval(func,2000); var i=0 ...

  4. golang 定时器

    上网查了下相关资料,基本上都介绍的是github.com\robfig\cron这个包来执行定时任务,试了下确实可以执行.但是此包下没有删 除任务的方法,只有暂停的方法(Stop),若要停止之前的任务 ...

  5. 一些css3的特效 javascript的window对象 定时器 延时器等ing...

    风车转动代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...

  6. JS延时器 定时器 暂停器 中断器

    // numberMillis 毫秒 function sleep(numberMillis) { var now = new Date(); var exitTime = now.getTime() ...

  7. [Golang]-7 定时器和打点器

    目录 定时器 打点器 After()方法 我们常常需要在未来某个时刻运行 Go 代码,或者在某段时间间隔内重复运行. Go 的内置 定时器 和 打点器 特性让这些很容易实现. 定时器 type Tim ...

  8. 谈谈Golang中goroutine的调度问题

    goroutine的调度问题,同样也是我之前面试的问题,不过这个问题我当时并不是很清楚,回来以后立马查阅资料,现整理出来备忘. 有一些预备知识需要说明,就是操作系统中的线程.操作系统中的线程分为两种: ...

  9. Go_20: Golang 中 time 包的使用

    time包中包括两类时间:时间点(某一时刻)和时常(某一段时间) 1. 时间常量(时间格式化) const ( ANSIC = "Mon Jan _2 15:04:05 2006" ...

随机推荐

  1. oracle相关常识

    1.数据类型 VARCHAR2() NUMBER() DATE CLOB BLOB 2.复制表:create table tableName as select * from emp3.新增列:ALT ...

  2. Java中Date()类 日期转字符串、字符串转日期的问题(已解决)

    Java中Date()类 日期转字符串.字符串转日期的问题 今天在写东西的时候突然发现一个问题,就是先new 一个Date()然后将生成的值转为字符串, 然后再将转换后的字符串再次用new Date( ...

  3. HDU 5215 Cycle(dfs判环)

    题意 题目链接 \(T\)组数据,给出\(n\)个点\(m\)条边的无向图,问是否存在一个奇环/偶环 Sol 奇环比较好判断吧,直接判是否是二分图就行了.. 偶环看起来很显然就是如果dfs到一个和他颜 ...

  4. jQuery Ajax(异步改同步)

    在实际使用中,我们经常会用的Ajax(异步加载,在不刷新整个网页的前提下对网页部分内容进行更新) 使用时,偶尔会遇上需要从一个接口中得到一个数组和数据对应的id,在另一个接口上再得到数据,最初写法如下 ...

  5. html网页访问WebAPI中的方法遇到的问题

      1.移动端访问远程服务时,建议使用WebAPI 2.用不同浏览器访问WebAPI时返回的文本格式是不同的,Chrome Firefox将在浏览器中以XML形式显示此列表,IE浏览器将获得Json格 ...

  6. java实现链表结构详细代码

    一.数据准备 1. 定义节点 2.   定义链表 1.数据部分 2.节点部分 class DATA //数据节点类型 { String key; String name; int age; } cla ...

  7. spring框架入门day01

    struts:web层,比较简单(ValueStack值栈,拦截器) hibernate:dao层,知识点杂 spring:service层,重要,讲多少用多少  --> [了解] spring ...

  8. 【NLP_Stanford课堂】正则表达式

    或者 [Ww]oods,方括号里的是或的关系,符合其一即被提出.用来匹配单个字符 [A-Z]:表示所有的大写字母之一 [a-z]:表示所有的小写字母之一 [0-9]:表示所有的0-9的数字之一 否定: ...

  9. KVM虚拟化学习记录

    请查看我的有道云笔记: http://note.youdao.com/noteshare?id=ed7308eeb1ee675b406494f4ba042ba4&sub=047FD8C61BA ...

  10. 一点一点学写Makefile-1

    相信很多Linux开发者 都得自己来写Makefile,刚开始学习学写这个的时候都会碰到很多困难,我之前没有自己独立完成过Makefile,都是在公司已有的模板上添加.现在突然有一个很大的想法就是从零 ...