十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
1、goroutine:在go语言中,每一个并发的执行单元叫做goroutine,如果一个程序中包含多个goroutine,对两个函数的调用则可能发生在同一时刻
成都创新互联公司网站建设由有经验的网站设计师、开发人员和项目经理组成的专业建站团队,负责网站视觉设计、用户体验优化、交互设计和前端开发等方面的工作,以确保网站外观精美、成都做网站、网站制作易于使用并且具有良好的响应性。
2、main goroutine:当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫他为main gorountine
3、go goroutine:新的goroutine会用go语句来创建,go+函数名,go语句会使其语句中的函数在一新创建的goroutine中运行,而go语句本身会迅速地完成
4、goroutine的退出:主函数返回时,所有的goroutine都会被直接打断,程序退出,除了从主函数退出或者终止程序之外,没有其他方法能够让一个goroutine来打断另一个的执行,但是可以通过另一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其他的goroutine,并让请求的goroutine自行结束执行
func startTimer(f func()) {
go func() {
for {
f()
now := time.Now()
// 计算下一个零点
next := now.Add(time.Hour * 24)
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
t := time.NewTimer(next.Sub(now))
-t.C
}
}()
}
在linux下实现定时器主要有如下方式
在这当中 基于时间轮方式实现的定时器 时间复杂度最小,效率最高,然而我们可以通过 优先队列 实现时间轮定时器。
优先队列的实现可以使用最大堆和最小堆,因此在队列中所有的数据都可以定义排序规则自动排序。我们直接通过队列中 pop 函数获取数据,就是我们按照自定义排序规则想要的数据。
在 Golang 中实现一个优先队列异常简单,在 container/head 包中已经帮我们封装了,实现的细节,我们只需要实现特定的接口就可以。
下面是官方提供的例子
因为优先队列底层数据结构是由二叉树构建的,所以我们可以通过数组来保存二叉树上的每一个节点。
改数组需要实现 Go 预先定义的接口 Len , Less , Swap , Push , Pop 和 update 。
timerType结构是定时任务抽象结构
首先的 start 函数,当创建一个 TimeingWheel 时,通过一个 goroutine 来执行 start ,在start中for循环和select来监控不同的channel的状态
通过for循环从队列中取数据,直到该队列为空或者是遇见第一个当前时间比任务开始时间大的任务, append 到 expired 中。因为优先队列中是根据 expiration 来排序的,
所以当取到第一个定时任务未到的任务时,表示该定时任务以后的任务都未到时间。
当 getExpired 函数取出队列中要执行的任务时,当有的定时任务需要不断执行,所以就需要判断是否该定时任务需要重新放回优先队列中。 isRepeat 是通过判断任务中 interval 是否大于 0 判断,
如果大于0 则,表示永久就生效。
防止外部滥用,阻塞定时器协程,框架又一次封装了timer这个包,名为 timer_wapper 这个包,它提供了两种调用方式。
参数和上面的参数一样,只是在第三个参数中使用了任务池,将定时任务放入了任务池中。定时任务的本身执行就是一个 put 操作。
至于put以后,那就是 workers 这个包管理的了。在 worker 包中, 也就是维护了一个任务池,任务池中的任务会有序的执行,方便管理。
简单学习了golang/go语言的基础语法,做个定时切割nginx日志的小脚本练习下,感觉挺好使的~
脚本代码如下,install后将脚本加入到crontab定时运行,当然golang也可以自己定时执行,这里加入到crontab运行,是因为golang进程有可能会被kill掉....
package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"strings"
"os/exec"
"io/ioutil"
)
func main(){
//日志目录
srcDirPath := "/usr/local/nginx/logs"
//存放切割日志目录
targetDirPath := "/usr/local/nginx/logs/history"
//ngixn进程ID文件
nginxPidPath := "/usr/local/nginx/logs/nginx.pid"
//检查存放切割日志目录是否存在,如果不存在则创建
finfo, errFile := os.Stat(targetDirPath)
if errFile !=nil {
errFile := os.MkdirAll(targetDirPath, 0777)
if errFile != nil {
fmt.Println("创建日志目录失败:"+errFile.Error())
return
}
} else if !finfo.IsDir() {
fmt.Println(targetDirPath+"已经存在且不是一个目录")
return
}
//获取当前日期,作为此次切割日志根目录
t := time.Now()
nowDateTime := t.Format("2006-01-02")
logPath := targetDirPath+"/"+nowDateTime
os.MkdirAll(logPath, 0777)
//获取nginx的进程ID
pfile,err := os.Open(nginxPidPath)
defer pfile.Close()
if err != nil {
fmt.Println("not found nginx pid file")
return
}
pidData,_ := ioutil.ReadAll(pfile)
pid := string(pidData)
pid = strings.Replace(pid,"\n","",-1)
//遍历日志目录
filepath.Walk(srcDirPath,func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
} else {
//获取切割日志路径
targetfilePath := strings.Replace(path,srcDirPath,logPath,1)
if strings.Index(targetfilePath,"nginx.pid") != -1 {
return nil
}
//移动文件
syscall.Rename(path,targetfilePath)
//创建原文件,这里不需要了,因为重启nginx后会自动生成滴
// nFile,errCreate := os.Create(path)
// if errCreate != nil {
// fmt.Println("create file faild:"+errCreate.Error())
// }
// defer nFile.Close()
}
return nil
})
//平滑重启nginx
cmd := exec.Command("kill","-USR1",pid)
_, errCmd := cmd.Output()
if errCmd != nil {
fmt.Println("重启nginx失败:"+errCmd.Error())
return;
}
fmt.Println("success")