设计原理
网络轮询器
所有的计时器都以最小四叉堆的形式**存储在处理器 runtime.p
**中。
网络轮询器实际上是对 I/O 多路复用技术的封装,网络轮询器并不是由运行时中的某一个线程独立运行的,运行时的调度器和系统调用都会通过 runtime.netpoll
与网络轮询器交换消息,获取待执行的 Goroutine 列表,并将待执行的 Goroutine 加入运行队列等待处理。
所有的文件 I/O、网络 I/O 和计时器都是由网络轮询器管理的,它是 Go 语言运行时重要的组成部分
处理器P与定时器相关的字段
type p struct {
...
timersLock mutex
timers []*timer
numTimers uint32
adjustTimers uint32
deletedTimers uint32
...
}
- timersLock:定时器互斥锁
- timers:最小四叉堆
- numTimers:处理器P中的定时器数量
- adjustTimers:处理器中处于
timerModifiedEarlier
状态的计时器数量; - deletedTimers:处理器中处于
timerDeleted
状态的计时器数量;
timer数据结构
runtime.timer
是计时器的结构体,每个timer计时器都存在最小四叉堆(runtime.p.timers
)内,他的数结构体:
type timer struct {
pp puintptr
when int64
period int64
f func(interface{}, uintptr)
arg interface{}
seq uintptr
nextwhen int64
status uint32
}
- when:当前计时器被唤醒的时间
- period:两次被唤醒的间隔
- f:每当计时器被唤醒时都会调用的函数
- arg:f的传参
- nextWhen:计时器处于
timerModifiedXX
状态时,用于设置when
字段 - status:计时器的状态
计时器对外暴露的结构体是:
type Timer struct {
C <-chan Time
r runtimeTimer
}
- C:订阅g
当计时器失效时,订阅计时器 Channel 的 Goroutine 会收到计时器失效的时间。
状态机
状态 | 解释 |
---|---|
timerNoStatus | 还没有设置状态 |
timerWaiting | 等待触发 |
timerRunning | 运行计时器函数 |
timerDeleted | 被删除 |
timerRemoving | 正在被删除 |
timerRemoved | 已经被停止并从堆中删除 |
timerModifying | 正在被修改 |
timerModifiedEarlier | 被修改到了更早的时间 |
timerModifiedLater | 被修改到了更晚的时间 |
timerMoving | 已经被修改正在被移动 |
计时器基本使用
timer
Timer类型代表单次时间事件。当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的
type Timer struct {
C <-chan Time
// 内含隐藏或非导出字段
}
-
func NewTimer(d Duration) *Timer
:NewTimer创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间。func Timer() { t := time.NewTimer(10 * time.Second) fmt.Println("start") //马上输出start fmt.Println(<-t.C) //阻塞等待channel唤醒此G,10秒后输出当前时间 }
-
func AfterFunc(d Duration, f func()) *Timer
:AfterFunc另起一个go协程,等待时间段d过去,然后调用f。它返回一个Timer,可以通过调用其Stop方法来取消等待和对f的调用。func Timer() { a := func() { fmt.Println("到期") } time.AfterFunc(10*time.Second, a) fmt.Println("start") } // 会打印“到期”,只要主协程没关闭
-
func (t *Timer) Reset(d Duration) bool
:Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。 -
func (t *Timer) Stop() bool
:Stop停止Timer的执行。如果停止了t会返回真;如果t已经被停止或者过期了会返回假。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。
ticker
Ticker保管一个通道,并每隔一段时间向其传递"tick"。
type Ticker struct {
C <-chan Time // 周期性传递时间信息的通道
// 内含隐藏或非导出字段
}
-
func NewTicker(d Duration) *Ticker
:NewTicker返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。如果d<=0会panic。关闭该Ticker可以释放相关资源。func Ticker() { t := time.NewTicker(time.Second * 5) for now := range t.C { //一直轮询读取chan,协程会被newticker网络轮询器唤醒 fmt.Println(now) } }
time包时间戳使用
-
type Time struct
:Time代表一个纳秒精度的时间点。 -
Now()
:Now返回当前本地时间。 -
func Parse(layout, value string) (Time, error)
:Parse解析一个格式化的时间字符串并返回它代表的时间。layout定义了参考时间:const timeFormat = "2006-01-02 15:04:05" if a, err := time.Parse(timeFormat, "2023-09-18 16:03:21"); err != nil { fmt.Println(err.Error()) } else { fmt.Println(a) //2023-09-18 16:03:21 +0000 UTC }
-
func (t Time) Date() (year int, month Month, day int)
:返回时间点t对应的年、月、日。 -
func (t Time) Clock() (hour, min, sec int)
:返回t对应的那一天的时、分、秒。 -
func (t Time) AddDate(years int, months int, days int) Time
:AddDate返回增加了给出的年份、月份和天数的时间点Time。例如,时间点January 1, 2011调用AddDate(-1, 2, 3)会返回March 4, 2010。fmt.Println(time.Now()) //2023-09-18 16:03:21.6799254 +0800 CST m=+0.009288501 fmt.Println(time.Now().AddDate(-1, 2, 3)) //2022-11-21 16:03:21.6799254 +0800 CST
-
func (t Time) Format(layout string) string
:Format根据layout指定的格式返回t代表的时间点的格式化文本表示const timeFormat = "2006-01-02 15:04:05" fmt.Println(time.Now().Format(timeFormat)) // 2022-11-21 16:53:08