我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解Golang中NewTimer计时器的底层实现原理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解Golang中NewTimer计时器的底层实现原理

1.简介

本文将介绍 Go 语言中的NewTimer,首先展示基于NewTimer创建的定时器来实现超时控制。接着通过一系列问题的跟进,展示了NewTimer的底层实现原理。

2.基本使用

我们首先通过一个简单的例子,来展示是怎么基于NewTimer实现超时控制的。

假设有一个需求,要求在 5 秒钟内完成某个任务,否则就认为任务失败。这时我们就可以使用 NewTimer 来实现超时控制。具体的实现步骤如下:

  • 首先,基于NewTimer创建一个 Timer 对象,设定超时时间为 5 秒钟。
  • 然后,启动一个 goroutine 来执行任务,并在任务完成后向通道发送一个完成信号。
  • 最后,使用 select 语句等待 Timer 的到期事件或任务完成信号,如果 Timer 先到期,就认为任务超时了,否则就认为任务完成了。

下面是一个简单的实现代码展示:

package main

import (
        "fmt"
        "time"
)

func main() {
        // 创建一个定时器,超时时间为 5 秒钟
        timer := time.NewTimer(5 * time.Second)

        // 启动一个 goroutine 执行任务,并在任务完成后向通道发送一个完成信号
        done := make(chan bool, 1)
        go func() {
                // 模拟任务执行耗时
                time.Sleep(2 * time.Second)
                done <- true
        }()

        // 等待任务完成或者超时
        select {
        case <-done:
                // 任务完成,输出完成信息
                fmt.Println("Task finished.")
        case <-timer.C:
                // 超时,输出超时信息
               fmt.Println("Task timed out.")
        }
}

在上述代码中,我们首先使用NewTimer创建了一个time.Timer对象,超时时间为 5 秒钟。然后启动一个 goroutine 来执行任务,并在任务完成后向通道 done 发送一个完成信号。最后使用 select 语句等待任务完成信号或者Timer的到期事件,如果Timer先到期,就认为任务超时了,否则就认为任务完成了。 在运行上述代码时,我们可以看到,在任务完成前 5 秒钟内,程序输出如下信息:

Task finished.

如果将任务完成时间改为超过 5 秒钟,程序将会在 5 秒钟后超时,输出如下信息:

Task timed out.

通过这个简单的例子,我们可以看到,如果任务在指定超时时间内完成,此时会执行正常的业务逻辑;如果任务未在指定的超时时间内完成,此时将走执行超时逻辑。

通过上述程序演示,我们展示了如何使用NewTimer创建的 time.Timer 实现超时控制的基本方法。

3.实现原理

3.1 内容分析

回顾上面的示例代码,我们实现超时控制的主要机制,是通过select语句同时监听两个channel,一个是任务执行状态的channel,一个是定时器的channel

当任务执行完成时,便通过channel对主协程进行通知。当定时器到达我们指定的时间,也就是超时时间,此时也通过定时器的channel进行通知。

 同时监听这两个channel,如果任务先执行完成,此时将会走select语句中正常业务逻辑case的代码,如果是在到达预定时间,任务仍没有完成,此时通过定时器channel进行通知,从而走超时业务逻辑case的代码,从而实现超时控制。

因此,这里主要的问题是,是如何在到达超时时间时,准时往定时器中的C对应的channel发送送数据,从而来告知其他协程,已经到达超时时间了呢,这个是如何做到的呢?

3.2 基本思路

下面先来看看NewTimer方法返回Timer结构体的内容,定义如下:

type Timer struct {
   C <-chan Time
   r runtimeTimer
}

可以看到,Timer结构体中C是一个chan Time类型的变量。在前面的代码的例子中,select语句是监听Timer结构体中的C变量,从中来读取数据的。

那么,当到达超时时间时,TimerC对应的channel将会有数据到达,那么肯定有其他地方,在到达超时时间时,会往Timer中的C发送数据。

那么现在的主要问题,是怎么做到当到达指定时间时,往Timer中的C发送数据呢?

其实,在go语言中,存在这样一个运行时函数startTimer ,定义如下:

func startTimer(*runtimeTimer)

它的作用是启动一个定时器,当定时器到期时,会执行相应的回调函数并传递回调参数。在 startTimer 函数内部,会使用系统调用来启动一个底层的操作系统定时器,等到定时器超时时,底层系统会自动触发一个信号(例如 Unix 平台上的 SIGALRM 信号),然后该信号将由 Go 运行时内部的信号处理函数捕获,并最终调用相关的回调函数。

那么,这里我们似乎可以使用startTimer来实现,当到达指定时间时,往channel发送数据,从而达到通知其他协程的效果。

3.3 实现步骤

首先,我们已经知道,startTimer能够启动一个定时器,当定时器到期时,会执行相应的回调函数并传递回调参数。而定时器的到期时间、回调函数以及回调函数的参数,则是通过runtimeTimer结构体传递过去的。

下面我们只需要runtimeTimer字段的含义,然后根据其含义,正确设置runtimeTimer结构体字段,调用startTimer方法启动一个定时器,就能够实现在指定时间时,执行某段逻辑。下面我们来看看runtimeTime的定义:

type runtimeTimer struct {
   pp       uintptr
   when     int64
   period   int64
   f        func(any, uintptr) // NOTE: must not be closure
   arg      any
   seq      uintptr
   nextwhen int64
   status   uint32
}

下面对runtimeTimer中的字段进行说明:

  • when int64:表示定时器应该在何时触发。该值的单位是纳秒
  • period int64:表示定时器的重复周期。如果定时器不需要重复触发,则该值为 0
  • f func(any, uintptr):指向定时器到期后需要执行的函数。
  • arg any:表示传递给定时器到期后执行的函数的参数,将传递给f的第一个参数
  • nextwhen int64:如果定时器是重复触发的,则 nextwhen 表示下一次触发的时间。
  • seq uintptr:用于防止定时器被错误的重置。当定时器被重置时,seq 会被更新。

基于对上面字段含义的理解,此时我们定义一个runtimeTimer结构体,然后调用startTimer,从而来实现能够在指定的某个时间点,往某个channel发送数据。具体实现如下:

// 定义一个channel,用于发送数据
c := make(chan Time, 1)
r := runtimeTimer{
      // 指定超时时间戳
      when: when(d),
      // 指定回调函数
      f: func sendTime(c any, seq uintptr) {
               select {
               case c.(chan Time) <- Now():
               default:
              }
        },
      // 传递给回调函数的参数
      arg:  c,
   },
}
// 调用startTimer启动一个定时器
startTimer(&t.r)

首先会创建一个带有缓冲的通道 c

接着初始化runtimeTimer结构体的值,设定好超时时间,回调函数以及参数。超时时间使用的是when函数来获取计数器的结束时间。when 函数会根据给定的时间间隔 d,返回一个绝对时间点,即计时器结束时间。

f字段指定的回调函数,则是将当前时间 Now() 发送到通道 c 中。当到达指定超时时间时,其将会调用回调函数f,同时将runtimeTimer结构体中arg字段的值,作为参数传递到回调函数当中。

然后调用startTimer启动一个定时器。当到达超时时间,将会调用回调函数,回调函数其会往一开始定义的channel发送数据。

至此,我们实现了最开始提到的,当到达指定时间时,往Timer中的C发送数据这个任务。

3.4 NewTimer的实现

回到我们的题目,NewTimer计时器的底层实现原理是什么?事实上,NewTimer创建的定时器,也确实是基于startTimer来实现的,下面我们来看看其实现:

func NewTimer(d Duration) *Timer {
   c := make(chan Time, 1)
   t := &Timer{
      C: c,
      r: runtimeTimer{
         when: when(d),
         f:    sendTime,
         arg:  c,
      },
   }
   startTimer(&t.r)
   return t
}

首先会创建一个带有缓冲的通道 c。然后创建一个 Timer 对象 t,将 c 通道赋值给 tC 属性。channel之后将作为回调函数的参数,同时也会作为Timer对象中C属性的值。这样子回调函数和Timer结构体中C变量与回调函数的channel事实上是共用一个channel的。

runtimeTimer结构体中的回调函数sendTime的实现与之前讲述的并无差异,都是将当前时间 Now() 发送到通道中,这里将不再赘述。

Timer结构体中C变量与回调函数的channel事实上是共用一个channel的,当到达超时时间,则会执行回调函数,往channel发送数据。而通过select语句对Timer中的channel进行监听的协程,此时也正常接收到通知了。

4.总结

在这篇文章中,我们首先展示了使用NewTimer创建一个定时器,然后基于此实现超时控制。我们发现,比较重要的一个机制是,timer对象是在到达超时时间时,timer对象中C对应的channel将会接收到数据。

timer对象在达到超时时间时,C属性对应的channel便能够接收到数据,这个是怎么做到的呢? 我们基于此进行了分析,事实上,首先是基于startTimer创建定时器,同时runtimeTimer结构体指定了回调函数,从而实现达到超时时间时,回调函数往C属性对应的channel发送数据。然后就能够基于该特性,能够实现超时控制等机制。

到此这篇关于详解Golang中NewTimer计时器的底层实现原理的文章就介绍到这了,更多相关Golang NewTimer计时器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解Golang中NewTimer计时器的底层实现原理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

详解Golang中NewTimer计时器的底层实现原理

本文将主要介绍一下Go语言中的NewTimer,首先展示基于NewTimer创建的定时器来实现超时控制。接着通过一系列问题的跟进,展示了NewTimer的底层实现原理,需要的可以参考一下
2023-05-18

深入理解Golang接口的底层实现原理

深入理解Golang接口的底层实现原理,需要具体代码示例Go语言(Golang)是一种由Google开发的开源编程语言,因其简洁、高效和并发特性而备受程序员青睐。在Go语言中,接口(interface)是一种非常重要的概念,它使代码更加灵
深入理解Golang接口的底层实现原理
2024-02-23

Spring注解Autowired的底层实现原理详解

从当前springboot的火热程度来看,java config的应用是越来越广泛了,在使用java config的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是@Autowired注解了。本文就来聊聊Autowired的底层实现原理
2022-11-13

PHP数组在底层的实现原理详解

PHP数组由哈希表和顺序元素数组实现。哈希表将键映射到元素数组索引,元素数组顺序存储元素。数组元素通过键哈希为哈希表索引,再利用索引获取元素数组中元素位置,快速高效访问。插入和删除元素时,哈希表会调整索引,维持数组结构。数组性能受哈希函数、哈希表大小和访问模式影响。优化策略包括选择合适哈希函数、调整哈希表大小、使用有序数组和避免哈希表重新哈希。
PHP数组在底层的实现原理详解
2024-04-02

GoLang中的timer定时器实现原理分析

Timer中对外暴露的只有一个channel,这个channel也是定时器的核心。当计时结束时,Timer会发送值到channel中,外部环境在这个channel收到值的时候,就代表计时器超时了,可与select搭配执行一些超时逻辑
2023-02-02

golang定时器Timer的用法和实现原理解析

这篇文章主要介绍了golang定时器Ticker,本文主要来看一下Timer的用法和实现原理,需要的朋友可以参考以下内容
2023-05-15

深入探究len函数在Python中的实现原理:深入理解其底层机制

深入理解Python中的len函数:掌握其底层实现原理,需要具体代码示例引言:Python是一门简洁、易读、容易上手的编程语言。在Python中,len()函数是一种非常常用的内置函数,用于返回某个容器对象(如字符串、列表、元组等)的元素
深入探究len函数在Python中的实现原理:深入理解其底层机制
2024-01-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录