1.前言
先说说为什么打算写一个定时器,Socket框架需要定时完成一些任务,比如发送心跳包或者定时向用户推送消息,接下来将简单实现多协程定时器单协程轮询定时器。
2.实现步骤
1.初始化并返回定时器
var timerStruct = util.NewTimer()
- 注册定时器
// 第一个参数:方法名称,第二个参数间隔多少秒执行一次,后面参数为方法需要传递的参数
timerStruct.RegisterTimer(cron1, "1s", 2,3,4, "tuzisir")
timerStruct.RegisterTimer(cron2, "3s")
3.执行定时器
timerStruct.ExecTimer()
3.代码示例
定时器核心代码
package util
import (
"time"
"reflect"
"log"
"strconv"
)
var timeUnit = []string{"h","s"}
// 方法
type funcInfoStruce struct {
Func interface{}
FuncTime int
FuncParams []interface{}
TimeUnit string
}
// 定时器结构
type TimerStruct struct {
RegisterFuncs map[string]*funcInfoStruce
WaitTimerFinsh chan struct{} // 测试使用
TimerNum int8 // 定时器数量充当map的键值
}
// 初始化返回定时器结构
func NewTimer() *TimerStruct {
return &TimerStruct{
RegisterFuncs:make(map[string]*funcInfoStruce),
WaitTimerFinsh:make(chan struct{}),
TimerNum:0,
}
}
// 注册定时器
func (t *TimerStruct) RegisterTimer(registerFunc interface{}, timeFormat string, params ... interface{}) bool {
// 判断时间格式
if len([]rune(timeFormat)) == 0 {
log.Print("时间格式没有输入")
return false
}
// 截取出左右两部分,左数值,右单位
lTimeFormat := timeFormat[0 : len(timeFormat)-1]
time,error := strconv.Atoi(lTimeFormat)
if error != nil{
log.Println("字符串转换成整数失败")
}
// 判断是否为数值
if !(time > 0) {
log.Print("不合法时间值")
return false
}
rTimeFormat := timeFormat[len(timeFormat)-1 : len(timeFormat)]
var issetFormat = false;
for _, v := range timeUnit {
if v == rTimeFormat {
issetFormat = true
break
}
}
if !issetFormat {
log.Print("使用默认时间格式s")
rTimeFormat = "s"
}
log.Print(time)
t.TimerNum++
fInfo := &funcInfoStruce{
Func:registerFunc,
FuncTime:time,
FuncParams:params,
TimeUnit:rTimeFormat,
}
t.RegisterFuncs[string(t.TimerNum)] = fInfo
return true
}
// 执行定时器
func (t *TimerStruct) ExecTimer() {
t.WaitTimerFinsh = make(chan struct{})
go func() {
t.rangeTimer()
}()
}
// 遍历定时器
func (t *TimerStruct) rangeTimer() {
// 遍历出注册的定时器方法
for _, v := range t.RegisterFuncs {
go t.execFunc(v)
}
}
// 执行方法
func (t *TimerStruct) execFunc(v *funcInfoStruce) {
var unit time.Duration
if v.TimeUnit == "h" {
unit = time.Hour
} else {
unit = time.Second
}
timerTool := time.NewTicker(time.Duration(v.FuncTime) * unit)
for {
select {
case <-timerTool.C:
f := reflect.ValueOf(v.Func)
in := make([]reflect.Value, len(v.FuncParams))
// 将方法参数拼装出来
for k, param := range v.FuncParams {
in[k] = reflect.ValueOf(param)
}
f.Call(in)
}
}
}
测试代码
package main
import (
"microSocket-master/util"
"log"
)
var timerStruct = util.NewTimer()
func cron1(a,b,c int, d string) {
log.Print(a,b,c,d)
}
func cron2() {
log.Printf("定时器2\r\n")
}
func main() {
log.Printf("1\r\n")
timerStruct.RegisterTimer(cron1, "1s", 2,3,4, "tuzisir")
timerStruct.RegisterTimer(cron2, "3s")
log.Printf("8\r\n")
timerStruct.ExecTimer()
// 测试使用,等待所有线程退出,测试代码永不退出
<-timerStruct.WaitTimerFinsh
log.Print("2")
}
执行效果
image.png
4.单协程轮询定时器
package main
import (
"time"
"log"
)
// 定时器任务结构体
type task struct {
funcs func() bool // 方法
time_dis int // 间隔多长时间执行
time_next int // 下一次需要执行的时间
}
// 定时器结构体
type timers struct {
tasks []*task
is_close chan bool
}
// 初始化返回定时器对象
func NewTimers() *timers{
return &timers{
tasks: make([]*task,0),
is_close: make(chan bool),
}
}
// 添加任务
func (this *timers) addTask (time_dis int, funcs func ()bool){
data := &task{
funcs:funcs,
time_dis:time_dis,
time_next:int(time.Now().Unix()) + time_dis,
}
this.tasks = append(this.tasks,data)
}
// 轮询查看需要执行的任务
func (this *timers)do(){
for _,v := range this.tasks{
if int64(v.time_next) < time.Now().Unix() {
v.time_next += v.time_dis
v.funcs()
}
}
}
// 执行定时器
func (this *timers) run(){
tick := time.NewTicker(1 * time.Second)
for {
select {
case <- this.is_close:
break
case <- tick.C:
this.do()
}
}
}
// 测试代码
func main(){
myTimer := NewTimers()
myTimer.addTask(2,func()bool{
log.Println(22222)
return true
})
myTimer.addTask(1,func()bool{
log.Println(11111)
return true
})
myTimer.run()
}