golang定时任务库cron实践
简介
cron一个用于管理定时任务的库,用 Go 实现 Linux 中crontab这个命令的效果。之前我们也介绍过一个类似的 Go 库——gron。gron代码小巧,用于学习是比较好的。但是它功能相对简单些,并且已经不维护了。如果有定时任务需求,还是建议使用cron。
快速使用
文本代码使用 Go Modules。
创建目录并初始化:
$ mkdir cron && cd cron $ go mod init cron
安装cron,目前最新稳定版本为 v3:
$ go get -u github.com/robfig/cron/v3
使用:
package mainimport ("fmt""time""github.com/robfig/cron/v3"
)func main() {c := cron.New()c.AddFunc("@every 1s", func() {fmt.Println("tick every 1 second")})c.Start()time.Sleep(time.Second * 5)
}
使用非常简单,创建cron对象,这个对象用于管理定时任务。
调用cron对象的AddFunc()方法向管理器中添加定时任务。AddFunc()接受两个参数,参数 1 以字符串形式指定触发时间规则,参数 2 是一个无参的函数,每次触发时调用。@every 1s表示每秒触发一次,@every后加一个时间间隔,表示每隔多长时间触发一次。例如@every 1h表示每小时触发一次,@every 1m2s表示每隔 1 分 2 秒触发一次。time.ParseDuration()支持的格式都可以用在这里。
调用c.Start()启动定时循环。
注意一点,因为c.Start()启动一个新的 goroutine 做循环检测,我们在代码最后加了一行time.Sleep(time.Second * 5)防止主 goroutine 退出。
运行效果,每隔 1s 输出一行字符串:
$ go run main.go tick every 1 second tick every 1 second tick every 1 second tick every 1 second tick every 1 second
时间格式
与Linux 中crontab命令相似,cron库支持用 5 个空格分隔的域来表示时间。这 5 个域含义依次为:
Minutes:分钟,取值范围[0-59],支持特殊字符* / , -;Hours:小时,取值范围[0-23],支持特殊字符* / , -;Day of month:每月的第几天,取值范围[1-31],支持特殊字符* / , - ?;Month:月,取值范围[1-12]或者使用月份名字缩写[JAN-DEC],支持特殊字符* / , -;Day of week:周历,取值范围[0-6]或名字缩写[JUN-SAT],支持特殊字符* / , - ?。
注意,月份和周历名称都是不区分大小写的,也就是说SUN/Sun/sun表示同样的含义(都是周日)。
特殊字符含义如下:
*:使用*的域可以匹配任何值,例如将月份域(第 4 个)设置为*,表示每个月;/:用来指定范围的步长,例如将小时域(第 2 个)设置为3-59/15表示第 3 分钟触发,以后每隔 15 分钟触发一次,因此第 2 次触发为第 18 分钟,第 3 次为 33 分钟。。。直到分钟大于 59;,:用来列举一些离散的值和多个范围,例如将周历的域(第 5 个)设置为MON,WED,FRI表示周一、三和五;-:用来表示范围,例如将小时的域(第 1 个)设置为9-17表示上午 9 点到下午 17 点(包括 9 和 17);?:只能用在月历和周历的域中,用来代替*,表示每月/周的任意一天。
了解规则之后,我们可以定义任意时间:
30 * * * *:分钟域为 30,其他域都是*表示任意。每小时的 30 分触发;30 3-6,20-23 * * *:分钟域为 30,小时域的3-6,20-23表示 3 点到 6 点和 20 点到 23 点。3,4,5,6,20,21,22,23 时的 30 分触发;0 0 1 1 *:1(第 4 个) 月 1(第 3 个) 号的 0(第 2 个) 时 0(第 1 个) 分触发。
记熟了这几个域的顺序,再多练习几次很容易就能掌握格式。熟悉规则了之后,就能熟练使用crontab命令了。
func main() {c := cron.New()c.AddFunc("30 * * * *", func() {fmt.Println("Every hour on the half hour")})c.AddFunc("30 3-6,20-23 * * *", func() {fmt.Println("On the half hour of 3-6am, 8-11pm")})c.AddFunc("0 0 1 1 *", func() {fmt.Println("Jun 1 every year")})c.Start()for {time.Sleep(time.Second)}
}
预定义时间规则
为了方便使用,cron预定义了一些时间规则:
@yearly:也可以写作@annually,表示每年第一天的 0 点。等价于0 0 1 1 *;@monthly:表示每月第一天的 0 点。等价于0 0 1 * *;@weekly:表示每周第一天的 0 点,注意第一天为周日,即周六结束,周日开始的那个 0 点。等价于0 0 * * 0;@daily:也可以写作@midnight,表示每天 0 点。等价于0 0 * * *;@hourly:表示每小时的开始。等价于0 * * * *。
例如:
func main() {c := cron.New()c.AddFunc("@hourly", func() {fmt.Println("Every hour")})c.AddFunc("@daily", func() {fmt.Println("Every day on midnight")})c.AddFunc("@weekly", func() {fmt.Println("Every week")})c.Start()for {time.Sleep(time.Second)}
}
上面代码只是演示用法,实际运行可能要等待非常长的时间才能有输出。
固定时间间隔
cron支持固定时间间隔,格式为:
@every <duration>
含义为每隔duration触发一次。<duration>会调用time.ParseDuration()函数解析,所以ParseDuration支持的格式都可以。例如1h30m10s。在快速开始部分,我们已经演示了@every的用法了,这里就不赘述了。
时区
默认情况下,所有时间都是基于当前时区的。当然我们也可以指定时区,有 2 两种方式:
- 在时间字符串前面添加一个
CRON_TZ=+ 具体时区,具体时区的格式在之前carbon的文章中有详细介绍。东京时区为Asia/Tokyo,纽约时区为America/New_York; - 创建
cron对象时增加一个时区选项cron.WithLocation(location),location为time.LoadLocation(zone)加载的时区对象,zone为具体的时区格式。或者调用已创建好的cron对象的SetLocation()方法设置时区。
示例:
func main() {nyc, _ := time.LoadLocation("America/New_York")c := cron.New(cron.WithLocation(nyc))c.AddFunc("0 6 * * ?", func() {fmt.Println("Every 6 o'clock at New York")})c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", func() {fmt.Println("Every 6 o'clock at Tokyo")})c.Start()for {time.Sleep(time.Second)}
}
Job接口
除了直接将无参函数作为回调外,cron还支持Job接口:
// cron.go
type Job interface {Run()
}
我们定义一个实现接口Job的结构:
type GreetingJob struct {Name string
}func (g GreetingJob) Run() {fmt.Println("Hello ", g.Name)
}
调用cron对象的AddJob()方法将GreetingJob对象添加到定时管理器中:
func main() {c := cron.New()c.AddJob("@every 1s", GreetingJob{"dj"})c.Start()time.Sleep(5 * time.Second)
}
运行效果:
$ go run main.go Hello dj Hello dj Hello dj Hello dj Hello dj
使用自定义的结构可以让任务携带状态(Name字段)。
实际上AddFunc()方法内部也调用了AddJob()方法。首先,cron基于func()类型定义一个新的类型FuncJob:
// cron.go type FuncJob func()
然后让FuncJob实现Job接口:
// cron.go
func (f FuncJob) Run() {f()
}
在AddFunc()方法中,将传入的回调转为FuncJob类型,然后调用AddJob()方法:
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {return c.AddJob(spec, FuncJob(cmd))
}
线程安全
cron会创建一个新的 goroutine 来执行触发回调。如果这些回调需要并发访问一些资源、数据,我们需要显式地做同步。
自定义时间格式
cron支持灵活的时间格式,如果默认的格式不能满足要求,我们可以自己定义时间格式。时间规则字符串需要cron.Parser对象来解析。我们先来看看默认的解析器是如何工作的。
首先定义各个域:
// parser.go const (Second ParseOption = 1 << iotaSecondOptional Minute Hour Dom Month Dow DowOptional Descriptor )
除了Minute/Hour/Dom(Day of month)/Month/Dow(Day of week)外,还可以支持Second。相对顺序都是固定的:
// parser.go
var places = []ParseOption{Second,Minute,Hour,Dom,Month,Dow,
}var defaults = []string{"0","0","0","*","*","*",
}
默认的时间格式使用 5 个域。
我们可以调用cron.NewParser()创建自己的Parser对象,以位格式传入使用哪些域,例如下面的Parser使用 6 个域,支持Second(秒):
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor, )
调用cron.WithParser(parser)创建一个选项传入构造函数cron.New(),使用时就可以指定秒了:
c := cron.New(cron.WithParser(parser))
c.AddFunc("1 * * * * *", func () {fmt.Println("every 1 second")
})
c.Start()
这里时间格式必须使用 6 个域,顺序与上面的const定义一致。
因为上面的时间格式太常见了,cron定义了一个便捷的函数:
// option.go
func WithSeconds() Option {return WithParser(NewParser(Second | Minute | Hour | Dom | Month | Dow | Descriptor,))
}
注意Descriptor表示对@every/@hour等的支持。有了WithSeconds(),我们不用手动创建Parser对象了:
c := cron.New(cron.WithSeconds())
选项
cron对象创建使用了选项模式,我们前面已经介绍了 3 个选项:
WithLocation:指定时区;WithParser:使用自定义的解析器;WithSeconds:让时间格式支持秒,实际上内部调用了WithParser。
cron还提供了另外两种选项:
WithLogger:自定义Logger;WithChain:Job 包装器。
WithLogger
WithLogger可以设置cron内部使用我们自定义的Logger:
func main() {c := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))c.AddFunc("@every 1s", func() {fmt.Println("hello world")})c.Start()time.Sleep(5 * time.Second)
}
上面调用cron.VerbosPrintfLogger()包装log.Logger,这个logger会详细记录cron内部的调度过程:
$ go run main.go cron: 2020/06/26 07:09:14 start cron: 2020/06/26 07:09:14 schedule, now=2020-06-26T07:09:14+08:00, entry=1, next=2020-06-26T07:09:15+08:00 cron: 2020/06/26 07:09:15 wake, now=2020-06-26T07:09:15+08:00 cron: 2020/06/26 07:09:15 run, now=2020-06-26T07:09:15+08:00, entry=1, next=2020-06-26T07:09:16+08:00 hello world cron: 2020/06/26 07:09:16 wake, now=2020-06-26T07:09:16+08:00 cron: 2020/06/26 07:09:16 run, now=2020-06-26T07:09:16+08:00, entry=1, next=2020-06-26T07:09:17+08:00 hello world cron: 2020/06/26 07:09:17 wake, now=2020-06-26T07:09:17+08:00 cron: 2020/06/26 07:09:17 run, now=2020-06-26T07:09:17+08:00, entry=1, next=2020-06-26T07:09:18+08:00 hello world cron: 2020/06/26 07:09:18 wake, now=2020-06-26T07:09:18+08:00 hello world cron: 2020/06/26 07:09:18 run, now=2020-06-26T07:09:18+08:00, entry=1, next=2020-06-26T07:09:19+08:00 cron: 2020/06/26 07:09:19 wake, now=2020-06-26T07:09:19+08:00 hello world cron: 2020/06/26 07:09:19 run, now=2020-06-26T07:09:19+08:00, entry=1, next=2020-06-26T07:09:20+08:0
我们看看默认的Logger是什么样的:
// logger.go
var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {return printfLogger{l, false}
}func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {return printfLogger{l, true}
}type printfLogger struct {logger interface{ Printf(string, ...interface{}) }logInfo bool
}
WithChain
Job 包装器可以在执行实际的Job前后添加一些逻辑:
- 捕获
panic; - 如果
Job上次运行还未结束,推迟本次执行; - 如果
Job上次运行还未介绍,跳过本次执行; - 记录每个
Job的执行情况。
我们可以将Chain类比为 Web 处理器的中间件。实际上就是在Job的执行逻辑外在封装一层逻辑。我们的封装逻辑需要写成一个函数,传入一个Job类型,返回封装后的Job。cron为这种函数定义了一个类型JobWrapper:
// chain.go type JobWrapper func(Job) Job
然后使用一个Chain对象将这些JobWrapper组合到一起:
type Chain struct {wrappers []JobWrapper
}func NewChain(c ...JobWrapper) Chain {return Chain{c}
}
调用Chain对象的Then(job)方法应用这些JobWrapper,返回最终的`Job:
func (c Chain) Then(j Job) Job {for i := range c.wrappers {j = c.wrappers[len(c.wrappers)-i-1](j)}return j
}
注意应用JobWrapper的顺序。
内置JobWrapper
cron内置了 3 个用得比较多的JobWrapper:
Recover:捕获内部Job产生的 panic;DelayIfStillRunning:触发时,如果上一次任务还未执行完成(耗时太长),则等待上一次任务完成之后再执行;SkipIfStillRunning:触发时,如果上一次任务还未完成,则跳过此次执行。
下面分别介绍。
Recover
先看看如何使用:
type panicJob struct {count int
}func (p *panicJob) Run() {p.count++if p.count == 1 {panic("oooooooooooooops!!!")}fmt.Println("hello world")
}func main() {c := cron.New()c.AddJob("@every 1s", cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(&panicJob{}))c.Start()time.Sleep(5 * time.Second)
}
panicJob在第一次触发时,触发了panic。因为有cron.Recover()保护,后续任务还能执行:
go run main.go cron: 2020/06/27 14:02:00 panic, error=oooooooooooooops!!!, stack=... goroutine 18 [running]: github.com/robfig/cron/v3.Recover.func1.1.1(0x514ee0, 0xc0000044a0)D:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/chain.go:45 +0xbc panic(0x4cf380, 0x513280)C:/Go/src/runtime/panic.go:969 +0x174 main.(*panicJob).Run(0xc0000140e8)D:/code/golang/src/github.com/darjun/go-daily-lib/cron/recover/main.go:17 +0xba github.com/robfig/cron/v3.Recover.func1.1()D:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/chain.go:53 +0x6f github.com/robfig/cron/v3.FuncJob.Run(0xc000070390)D:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/cron.go:136 +0x2c github.com/robfig/cron/v3.(*Cron).startJob.func1(0xc00005c0a0, 0x514d20, 0xc000070390)D:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/cron.go:312 +0x68 created by github.com/robfig/cron/v3.(*Cron).startJobD:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/cron.go:310 +0x7a hello world hello world hello world hello world
我们看看cron.Recover()的实现,很简单:
// cron.go
func Recover(logger Logger) JobWrapper {return func(j Job) Job {return FuncJob(func() {defer func() {if r := recover(); r != nil {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]err, ok := r.(error)if !ok {err = fmt.Errorf("%v", r)}logger.Error(err, "panic", "stack", "...\n"+string(buf))}}()j.Run()})}
}
就是在执行内层的Job逻辑前,添加recover()调用。如果Job.Run()执行过程中有panic。这里的recover()会捕获到,输出调用堆栈。
DelayIfStillRunning
还是先看如何使用:
type delayJob struct {count int
}func (d *delayJob) Run() {time.Sleep(2 * time.Second)d.count++log.Printf("%d: hello world\n", d.count)
}func main() {c := cron.New()c.AddJob("@every 1s", cron.NewChain(cron.DelayIfStillRunning(cron.DefaultLogger)).Then(&delayJob{}))c.Start()time.Sleep(10 * time.Second)
}
上面我们在Run()中增加了一个 2s 的延迟,输出中间隔变为 2s,而不是定时的 1s:
$ go run main.go 2020/06/27 14:11:16 1: hello world 2020/06/27 14:11:18 2: hello world 2020/06/27 14:11:20 3: hello world 2020/06/27 14:11:22 4: hello world
看看源码:
// chain.go
func DelayIfStillRunning(logger Logger) JobWrapper {return func(j Job) Job {var mu sync.Mutexreturn FuncJob(func() {start := time.Now()mu.Lock()defer mu.Unlock()if dur := time.Since(start); dur > time.Minute {logger.Info("delay", "duration", dur)}j.Run()})}
}
首先定义一个该任务共用的互斥锁sync.Mutex,每次执行任务前获取锁,执行结束之后释放锁。所以在上一个任务结束前,下一个任务获取锁是无法成功的,从而保证的任务的串行执行。
SkipIfStillRunning
还是先看看如何使用:
type skipJob struct {count int32
}func (d *skipJob) Run() {atomic.AddInt32(&d.count, 1)log.Printf("%d: hello world\n", d.count)if atomic.LoadInt32(&d.count) == 1 {time.Sleep(2 * time.Second)}
}func main() {c := cron.New()c.AddJob("@every 1s", cron.NewChain(cron.SkipIfStillRunning(cron.DefaultLogger)).Then(&skipJob{}))c.Start()time.Sleep(10 * time.Second)
}
输出:
$ go run main.go 2020/06/27 14:22:07 1: hello world 2020/06/27 14:22:10 2: hello world 2020/06/27 14:22:11 3: hello world 2020/06/27 14:22:12 4: hello world 2020/06/27 14:22:13 5: hello world 2020/06/27 14:22:14 6: hello world 2020/06/27 14:22:15 7: hello world 2020/06/27 14:22:16 8: hello world
注意观察时间,第一个与第二个输出之间相差 3s,因为跳过了两次执行。
注意DelayIfStillRunning与SkipIfStillRunning是有本质上的区别的,前者DelayIfStillRunning只要时间足够长,所有的任务都会按部就班地完成,只是可能前一个任务耗时过长,导致后一个任务的执行时间推迟了一点。SkipIfStillRunning会跳过一些执行。
看看源码:
func SkipIfStillRunning(logger Logger) JobWrapper {return func(j Job) Job {var ch = make(chan struct{}, 1)ch <- struct{}{}return FuncJob(func() {select {case v := <-ch:j.Run()ch <- vdefault:logger.Info("skip")}})}
}
定义一个该任务共用的缓存大小为 1 的通道chan struct{}。执行任务时,从通道中取值,如果成功,执行,否则跳过。执行完成之后再向通道中发送一个值,确保下一个任务能执行。初始发送一个值到通道中,保证第一个任务的执行。
总结
cron实现比较小巧,且优雅,代码行数也不多,非常值得一看!
相关文章:
golang定时任务库cron实践
简介 cron一个用于管理定时任务的库,用 Go 实现 Linux 中crontab这个命令的效果。之前我们也介绍过一个类似的 Go 库——gron。gron代码小巧,用于学习是比较好的。但是它功能相对简单些,并且已经不维护了。如果有定时任务需求,还…...
Julia 流程控制
流程控制语句通过程序设定一个或多个条件语句来实现。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。 Julia 提供了大量的流程控制语句: 复合表达式:begin 和 ;。 条件表达式:if-elseif-else 和 ?: (三…...
问题解决方案
前端开发 1、npm安装的时候老是卡住 reify:rxjs: timing reifyNode:node_modules/vue/cli/node_modules 查看当前使用的那个镜像 nrm lsnpm ---------- https://registry.npmjs.org/yarn --------- https://registry.yarnpkg.com/cnpm --------- https://r.cnpmjs.org/taobao …...
kubernetes基于helm部署gitlab-operator
kubernetes基于helm部署gitlab-operator 这篇博文介绍如何在 Kubernetes 中使用helm部署 GitLab-operator。 先决条件 已运行的 Kubernetes 集群负载均衡器,为ingress-nginx控制器提供EXTERNAL-IP,本示例使用metallb默认存储类,为gitlab p…...
ChatGPT在在线客服和呼叫中心中的应用如何?
ChatGPT在在线客服和呼叫中心领域中有广泛的应用潜力,可以帮助企业提供更高效、个性化和满意度更高的客户服务体验。以下是详细的讨论: **1. 自动化客服:** ChatGPT可以用于自动化客服流程,通过自动回复用户的常见问题和查询&…...
C++多线程环境下的单例类对象创建
使用C无锁编程实现多线程下的单例模式 贺志国 2023.8.1 在多线程环境下创建一个类的单例对象,要比单线程环境下要复杂很多。下面介绍在多线程环境下实现单例模式的几种方法。 一、尺寸较小的类单例对象创建 如果待创建的单例类SingletonForMultithread内包含的成…...
“深入解析JVM内部机制:从字节码到垃圾回收“
标题:深入解析JVM内部机制:从字节码到垃圾回收 摘要:本文将从字节码生成、类加载、运行时数据区域和垃圾回收等方面深入解析JVM的内部机制,并通过示例代码展示其工作原理和实践应用。 正文: 一、字节码生成 JVM是基…...
音频系统项目与音频算法研究方向分类
+我V hezkz17进数字音频系统研究开发交流答疑群(课题组) 音频系统项目与音频算法研究方向分类 一 音频系统项目产品分类 1 收音机,数字收音机,复读机 2 耳机,蓝牙耳机,TWS蓝牙耳机, 3 立体声音箱,AI智能音箱, 4 音频功放,车载功放, 5 音响,普通音响,Soundbar音响…...
单例模式和工厂模式
目录 今日良言:关关难过关关过,步步难行步步行 一、单例模式 1.饿汉模式 2.懒汉模式 二、工厂模式 今日良言:关关难过关关过,步步难行步步行 一、单例模式 首先来解释一下,什么是单例模式。 单例模式也就是单个…...
两个镜头、视野、分辨率不同的相机(rgb、红外)的视野校正
文章目录 背景实际效果查找资料资料1资料2 解决方案最终结果 背景 目前在做的项目用到两个摄像头,一个是热成像摄像头、另一个是普通的rgb摄像头。 一开始的目标是让他们像素级重合,使得点击rgb图像时,即可知道其像素对应的温度。但是在尝试…...
kettle 连接jdbc
DM JDBC连接 oracle JDBC连接 PG JDBC连接 SQLSERVER JDBC连接...
PyTorch中加载模型权重 A匹配B|A不匹配B
在做深度学习项目时,从头训练一个模型是需要大量时间和算力的,我们通常采用加载预训练权重的方法,而我们往往面临以下几种情况: 未修改网络,A与B一致 很简单,直接.load_state_dict() net ANet(num_cla…...
@FeignClient指定多个url实现负载均衡
C知道回答的如下: 在使用 FeignClient 调用多个 URL 实现负载均衡时,可以使用 Spring Cloud Ribbon 提供的功能来实现。下面是一个示例代码: 首先,在Spring Boot主类上添加EnableFeignClients注解启用Feign Client功能。 Spring…...
vue diff 双端比较算法
文章目录 双端指针比较策略命中策略四命中策略二命中策略三命中策略一未命中四种策略,遍历旧节点列表新增情况一新增情况二 删除节点双端比较的优势 双端指针 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children …...
初识React: 基础(概念 特点 高效原因 虚拟DOM JSX语法 组件)
1.什么是React? React是一个由Facebook开源的JavaScript库,它主要用于构建用户界面。React的特点是使用组件化的思想来构建界面,使得代码的可复用性和可维护性大大提高。React还引入了虚拟DOM的概念,减少了对真实DOM的直接操作,…...
自监督去噪:Neighbor2Neighbor原理分析与总结
文章目录 1. 方法原理1.1 先前方法总结1.2 Noise2Noise回顾1.3 从Noise2Noise到Neighbor2Neighbor1.4 框架结构2. 实验结果3. 总结 文章链接:https://arxiv.org/abs/2101.02824 参考博客:https://arxiv.org/abs/2101.02824 1. 方法原理 1.1 先前方法总…...
简单工厂模式(Simple Factory)
简单工厂模式,又称为静态工厂方法(Static Factory Method)模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个…...
Agent:OpenAI的下一步,亚马逊云科技站在第5层
什么是Agent?在大模型语境下,可以理解成能自主理解、规划、执行复杂任务的系统。Agent也将成为新的起点,成为各行各业构建新一代AI应用必不可少的组成部分。 对此,初创公司Seednapse AI创始人提出构建AI应用的五层基石理论&#…...
JMeter 4.x 简单使用
文章目录 前言JMeter 4.x 简单使用1. 启动2. 设置成中文3. 接口测试3.1. 设置线程组3.2. HTTP信息请求头管理器3.3. 添加HTTP请求默认值3.4. 添加HTTP cookie 管理3.5. 添加http请求3.5.1. 添加断言 3.6. 添加监听器-查看结果树3.7. 添加监听器-聚合报告 4. 测试 前言 如果您觉…...
深入NLTK:Python自然语言处理库高级教程
在前面的初级和中级教程中,我们了解了NLTK库中的基本和进阶功能,如词干提取、词形还原、n-gram模型和词云的绘制等。在本篇高级教程中,我们将深入探索NLTK的更多高级功能,包括句法解析、命名实体识别、情感分析以及文本分类。 一…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
