go语言计时器 go语言定时任务

计时器中四叉堆的变迁

在我们编码过程中,经常会用到与时间相关的需求。而关于时间转换之类的比较简单,那么计时器经过了以下几个版本的迭代:

10多年专业网站制作公司历程,坚持以创新为先导的网站服务,服务超过成百上千家企业及个人,涉及网站设计、重庆App定制开发、微信开发、平面设计、互联网整合营销等多个领域。在不同行业和领域给人们的工作和生活带来美好变化。

Go1.10之前的计时器的结构体如下所示

注意这个结构体是用var变量定义的,它会存储所有的计时器。t就是最小四叉堆,运行时创建的所有计时器都会加入到四叉堆中。

由一个独立的timerproc通过最小四叉堆和futexsleep来管理定时任务。

但是全局四叉堆共用一把锁对性能的影响非常大,所以Go1.10之后将全局四叉堆分割成了64个更小的四叉堆。

在理想情况下,四叉堆的数量应该等于处理器的数量GOMAXPROCS,但是需要动态获取处理的数量,所以经过权衡初始化64个四叉堆,如果当前机器的处理器P的个数超过了64个,多个处理器的计时器可能会存储在同一个桶中。

将全局计时器分片,虽然能够降低锁的粒度,但是timerproc造成处理器和线程之间频繁的上下文切换却成为了影响计时器的瓶颈。

在Go1.14版本之后,计时器桶timersBucket已经被移除了,所有的计时器都以最小四叉堆的形式存储在P中。

在p结构体中有以下字段与计时器关联:

而计时器的结构体为:

这个仅仅只是runtime/time.go运行时内部处理的结构,而真正对外暴露的计时器的结构体是:

通过channel来通知计时器时间

在runtime/time.go文件下,我们可以看到下面几个方法:

当通过time.NewTimer方法增加新的计时器时,会执行startTimer来增加计时器

状态从timerNoStatus-timerWaiting,其他状态会抛出异常

1、调用cleantimers清除处理器P中的计时器,可以加快创建和删除计时器的程序速度

2、调用doaddtimer将当前计时器加入到处理器P的四叉堆timers中

3、调用wakeNetPoller唤醒网络轮询器中休眠的线程,检查timer被唤醒的时间when是否在当前轮询预期的运行时间内,如果是就唤醒。

当通过调用timer.Stop停止计时器时,会执行stopTimer来停止计时器

deltimer会标记需要删除的计时器。在删除计时器的过程中,可能会遇到其他处理器P的计时器,所以我们仅仅只是将状态标记为删除,处理器P执行删除操作。

当通过调用timer.Reset重置定时器时,会执行resetTimer来重置定时器

modtimer会修改已经存在的计时器,会根据以下规则处理计时器状态

状态为timerNoStatus, timerRemoved会被标记为已删除wasRemoved,就会调用doaddtimer新创建一个计时器。

而在正常情况下会根据修改后的时间进行不同的处理:

会根据状态清除处理器P的最小四叉堆队头的计时器

在GPM调度的时候检查计时器

与cleantimers不同的是,adjusttimers会遍历处理器P转给你所有的计时器

会检查四叉堆堆顶的计时器,根据状态处理计时器

1、状态是timerDeleted,状态变为timerDeleted,然后删除计时器,再变更状态为timerRemoved

2、状态是timerModifiedXXX

3、状态是timerWaiting,如果计时器没有到达触发时间,直接返回,否则状态变为timerRunning,调用runOneTimer运行堆顶的计时器

根据period字段是否大于0判断,如果大于0

如果小于等于0:

更新完状态后,回调函数f(arg, seq)执行方法。

在adjesttimers中提到过

checkTimers是调度器用来运行处理器P中定时器的函数,会在以下几种情况被触发:

1、先通过处理器P字段中updateTimer0When判断是否有需要执行的计时器,如果没有直接返回

2、如果下一个计时器没有到期但是需要删除的计时器较少时会直接返回

3、加锁

4、需要处理的timer,根据时间将timers切片中的timer重新排序,调用adjusttimers

5、会通过runtimer依次查找运行计时器

6、处理器中已删除的timer大于p上的timer数量的1/4,对标记为timerDeleted的timer进行清理

7、解锁

go1.10最多可以创建GOMAXPROCS数量的timerproc协程,当然不超过64。但我们要知道timerproc自身就是协程,也需要runtime pmg的调度。到go 1.14把检查到期定时任务的工作交给了网络轮询器,不需要额外的调度,每次runtime.schedule和findrunable时直接运行到期的定时任务。

go 语言中的 rune

rune是Go语言中一种特殊的数据类型,它是int32的别名,几乎在所有方面等同于int32,用于区分字符值和整数值,官方解释如下:

下面我们通过一个例子来看一下:

我们猜测一下结果,hello5 个字符+1 个空格+3 个汉子,算起来应该是 9 个,长度为 9 才对,但是我们执行一下,

结果打印是 15,这是为什么呢?

所以计算出的长度就等于 5+1+3*3=15

如果我们需要计算出字符串的长度,而不是底层字节的个数,那么可以使用下面的方法:

运行结果如下:

在 rune 定义上方还有一个,byte = uint8

golang 获取时间精确能到纳秒吗

这样。不过只是个精确到纳秒的计时器,不是精确到纳秒的当前时间。windows好像只能拿到ms精度的当前时间吧,不是很清楚。

package main

import (

"syscall"

"time"

"unsafe"

)

func NewStopWatch() func() time.Duration {

var QPCTimer func() func() time.Duration

QPCTimer = func() func() time.Duration {

lib, _ := syscall.LoadLibrary("kernel32.dll")

qpc, _ := syscall.GetProcAddress(lib, "QueryPerformanceCounter")

qpf, _ := syscall.GetProcAddress(lib, "QueryPerformanceFrequency")

if qpc == 0 || qpf == 0 {

return nil

}

var freq, start uint64

syscall.Syscall(qpf, 1, uintptr(unsafe.Pointer(freq)), 0, 0)

syscall.Syscall(qpc, 1, uintptr(unsafe.Pointer(start)), 0, 0)

if freq = 0 {

return nil

}

freqns := float64(freq) / 1e9

return func() time.Duration {

var now uint64

syscall.Syscall(qpc, 1, uintptr(unsafe.Pointer(now)), 0, 0)

return time.Duration(float64(now-start) / freqns)

}

}

var StopWatch func() time.Duration

if StopWatch = QPCTimer(); StopWatch == nil {

// Fallback implementation

start := time.Now()

StopWatch = func() time.Duration { return time.Since(start) }

}

return StopWatch

}

func main() {

// Call a new stop watch to create one from this moment on.

watch := NewStopWatch()

// Do some stuff that takes time.

time.Sleep(1)

// Call the stop watch itself and it will return a time.Duration

dur := watch()

}

这和语言没关系,操作系统要提供这样的原语。linux和windows都是可以的。


本文名称:go语言计时器 go语言定时任务
URL分享:http://azwzsj.com/article/doicjce.html