网站首页 > 技术文章 正文
1. 定时器
何为定时器?
从语义来看:
定时器就是设定个指定时间,当到达了指定时间,则触发某件事件。
前端定时器
写过前端js的同学来说,为了实现某种定时触发效果,经常用到setInterval()与setTimeout()这两个方法。
其具体区别:
setInterval() 每隔指定时间就触发一次
setTimeout() 则是到了指定时间只触发一次
定时器分类:
- 周期性定时器(像闹钟一样,滴答、滴答、滴答......,持续不断)
- 一次性定时器(像炸弹一样,滴~ 蹦~,世界安静了)
2. Golang定时器
在golang提供的定时器功能中,也有这么两种常用的定时器:周期性的定时器Ticker,一次性的定时器Timer。
2.1 Timer
// 创建一次性定时器常用写法
func TheTimer() {
// 写法1
// func NewTimer(d Duration) *Timer
tim := time.NewTimer(time.Second)
// 写法2
// func After(d Duration) <-chan Time
tim2 := time.After(time.Second)
}
2.2 Ticker
// 创建周期性定时器常用写法
func TheTicker() {
// 写法1
// func NewTicker(d Duration) *Ticker
tim := time.NewTicker(time.Second)
// 写法2
// func Tick(d Duration) <-chan Time
tim2 := time.Tick(time.Second)
}
3. 定时器分析
func sendTime(c interface{}, seq uintptr) {
// Non-blocking send of time on c.
// Used in NewTimer, it cannot block anyway (buffer).
// Used in NewTicker, dropping sends on the floor is
// the desired behavior when the reader gets behind,
// because the sends are periodic.
select {
case c.(chan Time) <- Now():
default:
}
}
//type Timer struct {
// C <-chan Time
// r runtimeTimer
//}
// 创建一次性定时器
// 源码位置 time/sleep.go
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d), // 定时器指定的触发时间点
f: sendTime, // 到达了指定时间,会向通道c中发送数据
arg: c,
},
}
startTimer(&t.r) // 添加到定时器堆
return t
}
//type Ticker struct {
// C <-chan Time
// r runtimeTimer
//}
// 创建周期性定时器
// 源码位置 time/tick.go
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
// Give the channel a 1-element time buffer.
// If the client falls behind while reading, we drop ticks
// on the floor until the client catches up.
c := make(chan Time, 1)
t := &Ticker{
C: c,
r: runtimeTimer{
when: when(d), // 定时器指定的触发时间点
period: int64(d), // 通过该字段表明这是个周期性的定时器
f: sendTime, // 到达了指定时间,会向通道c中发送数据
arg: c,
},
}
startTimer(&t.r) // 添加到定时器堆
return t
}
通过分析源码发现,不仅timer、ticker底层对应的结构体内部结构一样,而且创建定时器对象时除了runtimeTimer.period初始化不一样外,其余完全一样。
也就是说,timer、ticker使用的相同的底层结构(类型名称不一样)以及处理逻辑,并通过runtimeTimer.period字段来区分是一次性还是周期性的定时器。
// 源码位置 runtime/time.go
// 添加定时器到timer堆
// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
if raceenabled {
racerelease(unsafe.Pointer(t))
}
addtimer(t) // 添加定时器
}
// addtimer adds a timer to the current P.
// This should only be called with a newly created timer.
// That avoids the risk of changing the when field of a timer in some P's heap,
// which could cause the heap to become unsorted.
func addtimer(t *timer) {
// when must never be negative; otherwise runtimer will overflow
// during its delta calculation and never expire other runtime timers.
if t.when < 0 {
t.when = maxWhen
}
if t.status != timerNoStatus {
throw("addtimer called with initialized timer")
}
t.status = timerWaiting
when := t.when
pp := getg().m.p.ptr()
lock(&pp.timersLock)
cleantimers(pp)
doaddtimer(pp, t) // 具体添加定时器逻辑
unlock(&pp.timersLock)
wakeNetPoller(when)
}
// doaddtimer adds t to the current P's heap.
// The caller must have locked the timers for pp.
func doaddtimer(pp *p, t *timer) {
// Timers rely on the network poller, so make sure the poller
// has started.
if netpollInited == 0 {
netpollGenericInit()
}
if t.pp != 0 {
throw("doaddtimer: P already set in timer")
}
t.pp.set(pp)
i := len(pp.timers)
pp.timers = append(pp.timers, t) // pp.timers 为具体的定时器切片,追加当前定时器t
siftupTimer(pp.timers, i)
if t == pp.timers[0] {
atomic.Store64(&pp.timer0When, uint64(t.when))
}
atomic.Xadd(&pp.numTimers, 1)
}
/*
//源码位置 runtime/runtime2.go
type p struct {
......
// The when field of the first entry on the timer heap.
// This is updated using atomic functions.
// This is 0 if the timer heap is empty.
timer0When uint64
......
// Actions to take at some time. This is used to implement the
// standard library's time package.
// Must hold timersLock to access.
timers []*timer
......
}
*/
通过源码可以发现,定时器添加到了当前G所属的P中(Golang著名的GPM模型)。
// 源码位置 runtime/time.go
// 执行一个定时器
// runOneTimer runs a single timer.
// The caller must have locked the timers for pp.
// This will temporarily unlock the timers while running the timer function.
//go:systemstack
func runOneTimer(pp *p, t *timer, now int64) {
if raceenabled {
ppcur := getg().m.p.ptr()
if ppcur.timerRaceCtx == 0 {
ppcur.timerRaceCtx = racegostart(funcPC(runtimer) + sys.PCQuantum)
}
raceacquirectx(ppcur.timerRaceCtx, unsafe.Pointer(t))
}
f := t.f // 生成定时器对象时的 func sendTime(c interface{}, seq uintptr) 方法
arg := t.arg // 生成定时器对象时的 通道 c
seq := t.seq
if t.period > 0 { // 发现是周期性定时器
// Leave in heap but adjust next time to fire.
delta := t.when - now
t.when += t.period * (1 + -delta/t.period) // 下次定时器触发的时间
siftdownTimer(pp.timers, 0)
if !atomic.Cas(&t.status, timerRunning, timerWaiting) {
badTimer()
}
updateTimer0When(pp) // 调整timer堆
} else {
// 发现是一次性定时器,则从timer堆中移除
// Remove from heap.
dodeltimer0(pp) // 从timer堆中移除
if !atomic.Cas(&t.status, timerRunning, timerNoStatus) {
badTimer()
}
}
if raceenabled {
// Temporarily use the current P's racectx for g0.
gp := getg()
if gp.racectx != 0 {
throw("runOneTimer: unexpected racectx")
}
gp.racectx = gp.m.p.ptr().timerRaceCtx
}
unlock(&pp.timersLock)
f(arg, seq) // 执行 func sendTime(c interface{}, seq uintptr) 方法,即向通道c发送数据
lock(&pp.timersLock)
if raceenabled {
gp := getg()
gp.racectx = 0
}
}
4. 定时器写法比较
// 定时器 坏的写法
// 每次都会生成新的定时器对象插入到timer堆中
func tickBad() {
for {
select {
case <-time.After(time.Second):
fmt.Println("timer")
case <-time.Tick(time.Second):
fmt.Println("tick")
}
}
}
// 定时器 好的写法
// 每次都复用同一个定时器对象
func tickGood() {
timer := time.After(time.Second)
ticker := time.Tick(time.Second)
for {
select {
case <-timer:
fmt.Println("timer")
case <-ticker:
fmt.Println("ticker")
}
}
}
总结
1. 在Go中,无论是周期性定时器Ticker,还是一次性定时器Timer,其底层逻辑以及数据结构完全一致。其区别主要是通过生成定时器对象时period字段的初始化来标明是一次性还是周期性。
2. 在定时器执行时,通过period发现是周期性定时器的话,会对该定时器下次的事件到达时间进行更新,并更新整个堆。如果是一次性定时器的话,则会从堆中移除。
3. 值得注意的是,在定时器代码逻辑的书写中如果是写在for{} 循环当中,则需要把定时器对象写在for{}循环外面,否则每次循环都是生成新的定时器对象添加到定时器堆中,时间久了会出现意想不到的异常情况。
猜你喜欢
- 2024-10-05 聊聊浏览器的事件循环 浏览器循环点击插件
- 2024-10-05 vue 基础-生命周期 lifecycle 的执行顺序和作用
- 2024-10-05 前端如何搞监控总结篇 前端实时监控界面
- 2024-10-05 JavaScript setTimeout要理解 js中settime
- 2024-10-05 描述React的组件生命周期方法,并解释它们在何时被调用。
- 2024-10-05 面试必备-setTimeout vs setInterval哪个更准确,0ms延迟的真相
- 2024-10-05 autolog.js:一个小而美的toast插件。
- 2024-10-05 JavaScript 事件循环:理解进程、线程和异步编程
- 2024-10-05 高级前端进阶,你了解事件循环吗?
- 2024-10-05 「中高级前端」高性能渲染十万条数据(时间分片)
你 发表评论:
欢迎- 05-10如何优化数据库和前端之间的交互?
- 05-10前端代码优化小秘籍(前端优化24条建议)
- 05-10VS Code当中的15个神仙插件,值得收藏
- 05-10如何自己开发一个Google浏览器插件?
- 05-10前端流行框架Vue3教程:14. 组件传递Props效验
- 05-10吃了一年的SU,最好用的插件都在这了
- 05-10前端必看!这款神器让网站界面告别千篇一律
- 05-10程序员请收好:10个非常有用的 Visual Studio Code 插件
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端懒加载 (45)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle查询数据库 (45)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)