Go基于协程池的延迟任务调度器
原理
通过用一个goroutine以及堆来存储要待调度的延迟任务,当达到调度时间后,将其添加到协程池中去执行。
主要是使用了chan、Mutex、atomic及ants协程池来实现。
用途
主要是用于高并发及大量定时任务要处理的情况,如果使用Go协程来实现每次延迟任务的调度,那么数量极大的goroutine将会占用内存,导致性能下降,使用协程池实现延迟任务的调度,会改善该情况。
如在物联网设备中,当连接数量达到几十万时,如果使用goroutine来处理心跳或者活跃检测,频繁的创建销毁goroutine会影响性能。
特色
在常见的cron等开源框架中使用的是数组存储待调度的任务,每次循环时都要排序,并且要删除某个任务则时间复杂度是O(n)。
本文通过使用堆及双重Map优化存储待调度的任务,使得添加任务时间复杂度为O(log n),获取任务时间复杂度为O(1),删除时间复杂度为O(1)。
调度器并不会真正的删除取消任务,当取消任务达到执行时间时,会直接continue,是为了提高删除效率,如果要删除取消任务,那么删除的时间复杂度为O(log n),当有极大量任务时,会占用一些内存,通过空间换时间来提高删除效率,下文也提供了删除取消任务的实现,根据不同的场景使用不同的定时任务。
API
创建
NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) //创建协程数是1的延迟任务调度器
s, _ := NewSchedule(1)
创建一个延迟调度任务器,workerNum是协程数量,options是ants协程池的配置,除了WithMaxBlockingTasks不能配置,别的都可以,具体参考:https://github.com/panjf2000/ants
调度一次
func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (TaskId, error) //1秒后打印一次时间
taskId, _ := s.ScheduleOne(func() {fmt.Println(time.Now())
}, time.Second)
重复调度
func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次时间
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)
取消调度
func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次时间
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)
//休眠3秒后,取消调度
time.Sleep(3 * time.Second)
s.CancelTask(taskId)
停止调度
func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次时间
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)
//休眠3秒后,停用延迟任务调度器
time.Sleep(3 * time.Second)
s.Shutdown()
代码
package scheduleimport ("container/heap""errors""github.com/panjf2000/ants/v2""math""sync/atomic""time"
)var (// ErrScheduleShutdown 延迟任务调度器已关闭错误ErrScheduleShutdown = errors.New("schedule: schedule is already in shutdown")
)const invalidTaskId = 0type TaskId uint32
type OriginalTaskId uint32// Schedule 延迟调度的结构体,提供延迟调度任务的全部方法
// 通过NewSchedule方法创建Schedule,通过Schedule、ScheduleOne方法添加延迟调度任务,通过CancelTask方法取消任务,通过Shutdown停止延迟任务
type Schedule struct {//任务堆,按时间排序taskHeap taskHeap//可执行的任务Map,key是当前的任务id,value是任务的第一次原始id,用于优化取消任务时需要遍历堆去删除executeTaskIdMap map[TaskId]OriginalTaskId//任务id的Map,key是任务的第一次原始id,value是当前的任务id,用于优化取消任务时需要遍历堆去删除originalTaskIdMap map[OriginalTaskId]TaskId//调度器是否运行中running atomic.Bool//下一个任务idnextTaskId atomic.Uint32//任务运行池pool *ants.Pool//添加任务ChanaddTaskChan chan *Task//删除任务ChanstopTaskChan chan struct{}//取消任务ChancancelTaskChan chan OriginalTaskId
}// NewSchedule 构建一个Schedule
// workerNum 工作的协程数量,options ants协程池的配置,除了WithMaxBlockingTasks不能配置,别的都可以,具体参考:https://github.com/panjf2000/ants
func NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) {//延迟任务的最大任务数量必须不限制options = append(options, ants.WithMaxBlockingTasks(0))//创建一个协程池pool, err := ants.NewPool(workerNum)if err != nil {return nil, err}//创建一个延迟调度结构体s := &Schedule{taskHeap: make(taskHeap, 0),executeTaskIdMap: make(map[TaskId]OriginalTaskId),originalTaskIdMap: make(map[OriginalTaskId]TaskId),running: atomic.Bool{},nextTaskId: atomic.Uint32{},pool: pool,addTaskChan: make(chan *Task),stopTaskChan: make(chan struct{}),cancelTaskChan: make(chan OriginalTaskId),}//启动调度 会开启一个协程去将即将要调度的任务添加到协程池中运行s.start()return s, nil
}// ScheduleOne 添加延迟调度任务,只调度一次
// job 执行的方法 duration 周期间隔,如果是负数立马执行,如果是负数立马且只执行一次
func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, true)
}// Schedule 添加延迟调度任务,重复调度
// job 执行的方法 duration 周期间隔,如果是负数立马且只执行一次
func (s *Schedule) Schedule(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, false)
}// doSchedule 添加延迟调度任务的具体实现
func (s *Schedule) doSchedule(job func(), duration time.Duration, onlyOne bool) (uint32, error) {if s.running.Load() {//如果是负数 只执行一次if duration <= 0 {onlyOne = true}nextTaskId := s.getNextTaskId()task := new(Task)task.job = jobtask.executeTime = time.Now().Add(duration)task.onlyOne = onlyOnetask.duration = durationtask.originalId = OriginalTaskId(nextTaskId)task.id = TaskId(nextTaskId)s.addTaskChan <- taskreturn uint32(task.originalId), nil} else {return invalidTaskId, ErrScheduleShutdown}
}// CancelTask 取消延迟调度任务
// taskId 任务id
func (s *Schedule) CancelTask(taskId uint32) {if s.running.Load() {if taskId != invalidTaskId {s.cancelTaskChan <- OriginalTaskId(taskId)}}
}// Shutdown 结束延迟任务调度
func (s *Schedule) Shutdown() {//通过cas设值if s.running.CompareAndSwap(true, false) {s.stopTaskChan <- struct{}{}}
}// IsShutdown 延迟任务调度是否关闭
func (s *Schedule) IsShutdown() bool {return !s.running.Load()
}// start 启动延迟任务调度
func (s *Schedule) start() {s.running.Store(true)go func() {for {now := time.Now()var timer *time.Timer//如果没有任务提交,睡眠等待任务if s.taskHeap.Len() == 0 {timer = time.NewTimer(math.MaxUint16 * time.Hour)} else {//查看第一个要执行的任务是否是被取消的task := s.taskHeap.Peek()_, ok := s.executeTaskIdMap[task.id]if !ok {//是被取消的任务,移除后continueheap.Pop(&s.taskHeap)continue} else {//设置执行间隔timer = time.NewTimer(task.executeTime.Sub(now))}}select {case <-timer.C://到达第一个任务执行时间task := heap.Pop(&s.taskHeap).(*Task)//提交到线程池执行,返回的error不需要处理,因为任务池是无限大_ = s.pool.Submit(task.job)//单次执行则删除,多次执行,则更新if task.onlyOne {s.removeTask(task.originalId, task.id)} else {s.updateTask(task)}case originalTaskId := <-s.cancelTaskChan:timer.Stop()//如果取消的任务id在待执行任务列表中,则删除任务if taskId, ok := s.originalTaskIdMap[originalTaskId]; ok {s.removeTask(originalTaskId, taskId)}case task := <-s.addTaskChan:timer.Stop()//添加任务s.addTask(task)case <-s.stopTaskChan:timer.Stop()//关闭资源s.close()return}}}()
}// updateTask 更新延迟调度任务
func (s *Schedule) updateTask(executedTask *Task) {//拷贝 并设置新的执行时间和IDtask := *executedTasktask.executeTime = time.Now().Add(task.duration)nextTaskId := s.getNextTaskId()task.id = TaskId(nextTaskId)//把已执行的任务删除s.removeTask(invalidTaskId, executedTask.id)//添加新的任务s.addTask(&task)
}// removeTask 移除任务
func (s *Schedule) removeTask(originalTaskId OriginalTaskId, taskId TaskId) {//如果原始的任务ID不为空,则为使用者取消的,从任务Map中也删除if originalTaskId != invalidTaskId {delete(s.originalTaskIdMap, originalTaskId)}delete(s.executeTaskIdMap, taskId)
}// addTask 添加任务
func (s *Schedule) addTask(task *Task) {s.originalTaskIdMap[task.originalId] = task.ids.executeTaskIdMap[task.id] = task.originalIdheap.Push(&s.taskHeap, task)
}// getNextTaskId 获取下一个任务id
func (s *Schedule) getNextTaskId() uint32 {taskId := s.nextTaskId.Add(1)if taskId == invalidTaskId {taskId = s.nextTaskId.Add(1)}return taskId
}// close 关闭Schedule资源和协程池的资源
func (s *Schedule) close() {//关闭所有资源并设置为 nil help gcs.taskHeap = nils.executeTaskIdMap = nils.originalTaskIdMap = nils.pool.Release()s.pool = nilclose(s.addTaskChan)close(s.cancelTaskChan)close(s.stopTaskChan)s.addTaskChan = nils.cancelTaskChan = nils.stopTaskChan = nil
}// Task 调度任务结构体,是一个调度任务的实体信息
type Task struct {// 原始id,用于Schedule本身的删除使用,用两层Map的方式优化数组删除的O(n)时间复杂度originalId OriginalTaskId// 任务idid TaskId// 执行的时间,每次执行完,如果重复调度就重新计算executeTime time.Time// 周期间隔duration time.Duration// 执行的任务job func()// 是否只执行一次onlyOne bool
}// 任务的堆,使用队只需要在添加的时候进行排序,堆顶是最先要执行的任务
type taskHeap []*Task// 下面都是堆接口的实现func (t *taskHeap) Len() int {return len(*t)
}
func (t *taskHeap) Less(i, j int) bool {return (*t)[i].executeTime.Before((*t)[j].executeTime)
}func (t *taskHeap) Swap(i, j int) {(*t)[i], (*t)[j] = (*t)[j], (*t)[i]
}func (t *taskHeap) Push(x interface{}) {*t = append(*t, x.(*Task))
}func (t *taskHeap) Pop() interface{} {old := *tn := len(old)x := old[n-1]old[n-1] = nil*t = old[:n-1]return x
}// Peek 查看堆顶元素,非堆接口的实现
func (t *taskHeap) Peek() *Task {return (*t)[0]
}
代码加上详细的中文注解,大约300行。
github地址:
https://github.com/xzc-coder/go-schedule
另一个版本的实现,删除时间复杂度为:O(log n),相对上文中的实现,占用的内存会少,但是删除效率会变低。
package scheduleimport ("container/heap""errors""github.com/panjf2000/ants/v2""math""sync/atomic""time"
)var (// ErrScheduleShutdown 延迟任务调度器已关闭错误ErrScheduleShutdown = errors.New("schedule: schedule is already in shutdown")
)const invalidTaskId = 0type TaskId uint32// Schedule 延迟调度的结构体,提供延迟调度任务的全部方法
// 通过NewSchedule方法创建Schedule,通过Schedule、ScheduleOne方法添加延迟调度任务,通过CancelTask方法取消任务,通过Shutdown停止延迟任务
type Schedule struct {//任务堆,按时间排序taskHeap taskHeaptaskMap map[TaskId]*Task//调度器是否运行中running atomic.Bool//下一个任务idnextTaskId atomic.Uint32//任务运行池pool *ants.Pool//添加任务ChanaddTaskChan chan *Task//删除任务ChanstopTaskChan chan struct{}//取消任务ChancancelTaskChan chan TaskId
}// NewSchedule 构建一个Schedule
// workerNum 工作的协程数量,options ants协程池的配置,除了WithMaxBlockingTasks不能配置,别的都可以,具体参考:https://github.com/panjf2000/ants
func NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) {//延迟任务的最大任务数量必须不限制options = append(options, ants.WithMaxBlockingTasks(0))//创建一个协程池pool, err := ants.NewPool(workerNum)if err != nil {return nil, err}//创建一个延迟调度结构体s := &Schedule{taskHeap: make(taskHeap, 0),taskMap: make(map[TaskId]*Task),running: atomic.Bool{},nextTaskId: atomic.Uint32{},pool: pool,addTaskChan: make(chan *Task),stopTaskChan: make(chan struct{}),cancelTaskChan: make(chan TaskId),}//启动调度 会开启一个协程去将即将要调度的任务添加到协程池中运行s.start()return s, nil
}// ScheduleOne 添加延迟调度任务,只调度一次
// job 执行的方法 duration 周期间隔,如果是负数立马执行,如果是负数立马且只执行一次
func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, true)
}// Schedule 添加延迟调度任务,重复调度
// job 执行的方法 duration 周期间隔,如果是负数立马且只执行一次
func (s *Schedule) Schedule(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, false)
}// doSchedule 添加延迟调度任务的具体实现
func (s *Schedule) doSchedule(job func(), duration time.Duration, onlyOne bool) (uint32, error) {if s.running.Load() {//如果是负数 只执行一次if duration <= 0 {onlyOne = true}nextTaskId := s.getNextTaskId()task := new(Task)task.job = jobtask.executeTime = time.Now().Add(duration)task.onlyOne = onlyOnetask.duration = durationtask.id = TaskId(nextTaskId)task.index = 0s.addTaskChan <- taskreturn uint32(task.id), nil} else {return invalidTaskId, ErrScheduleShutdown}
}// CancelTask 取消延迟调度任务
// taskId 任务id
func (s *Schedule) CancelTask(taskId uint32) {if s.running.Load() {if taskId != invalidTaskId {s.cancelTaskChan <- TaskId(taskId)}}
}// Shutdown 结束延迟任务调度
func (s *Schedule) Shutdown() {//通过cas设值if s.running.CompareAndSwap(true, false) {s.stopTaskChan <- struct{}{}}
}// IsShutdown 延迟任务调度是否关闭
func (s *Schedule) IsShutdown() bool {return !s.running.Load()
}// start 启动延迟任务调度
func (s *Schedule) start() {s.running.Store(true)go func() {for {now := time.Now()var timer *time.Timer//如果没有任务提交,睡眠等待任务if s.taskHeap.Len() == 0 {timer = time.NewTimer(math.MaxUint16 * time.Hour)} else {task := s.taskHeap.Peek()//设置执行间隔timer = time.NewTimer(task.executeTime.Sub(now))}select {case <-timer.C://到达第一个任务执行时间task := heap.Pop(&s.taskHeap).(*Task)//提交到线程池执行,返回的error不需要处理,因为任务池是无限大_ = s.pool.Submit(task.job)//单次执行则删除,多次执行,则更新if task.onlyOne {s.removeTask(false, task)} else {s.updateTask(task)}case taskId := <-s.cancelTaskChan:timer.Stop()//如果取消的任务id在待执行任务列表中,则删除任务if task, ok := s.taskMap[taskId]; ok {s.removeTask(true, task)}case task := <-s.addTaskChan:timer.Stop()//添加任务s.addTask(task)case <-s.stopTaskChan:timer.Stop()//关闭资源s.close()return}}}()
}// updateTask 更新延迟调度任务
func (s *Schedule) updateTask(executedTask *Task) {//拷贝 并设置新的执行时间和IDtask := *executedTasktask.executeTime = time.Now().Add(task.duration)//把已执行的任务删除s.removeTask(false, executedTask)//添加新的任务s.addTask(&task)
}// removeTask 移除任务
func (s *Schedule) removeTask(removeHeap bool, task *Task) {//从Map和堆中delete(s.taskMap, task.id)if removeHeap {heap.Remove(&s.taskHeap, task.index)}
}// addTask 添加任务
func (s *Schedule) addTask(task *Task) {heap.Push(&s.taskHeap, task)s.taskMap[task.id] = task
}// getNextTaskId 获取下一个任务id
func (s *Schedule) getNextTaskId() uint32 {taskId := s.nextTaskId.Add(1)if taskId == invalidTaskId {taskId = s.nextTaskId.Add(1)}return taskId
}// close 关闭Schedule资源和协程池的资源
func (s *Schedule) close() {//关闭所有资源并设置为 nil help gcs.taskHeap = nils.taskMap = nils.pool.Release()s.pool = nilclose(s.addTaskChan)close(s.cancelTaskChan)close(s.stopTaskChan)s.addTaskChan = nils.cancelTaskChan = nils.stopTaskChan = nil
}// Task 调度任务结构体,是一个调度任务的实体信息
type Task struct {// 任务idid TaskId// 执行的时间,每次执行完,如果重复调度就重新计算executeTime time.Time// 周期间隔duration time.Duration// 执行的任务job func()// 是否只执行一次onlyOne bool//所在堆数组的下标位置index int
}// 任务的堆,使用队只需要在添加的时候进行排序,堆顶是最先要执行的任务
type taskHeap []*Task// 下面都是堆接口的实现func (t *taskHeap) Len() int {return len(*t)
}
func (t *taskHeap) Less(i, j int) bool {return (*t)[i].executeTime.Before((*t)[j].executeTime)
}func (t *taskHeap) Swap(i, j int) {(*t)[i], (*t)[j] = (*t)[j], (*t)[i](*t)[i].index = i(*t)[j].index = j
}func (t *taskHeap) Push(x interface{}) {*t = append(*t, x.(*Task))
}func (t *taskHeap) Pop() interface{} {old := *tn := len(old)x := old[n-1]old[n-1] = nil*t = old[:n-1]return x
}// Peek 查看堆顶元素,非堆接口的实现
func (t *taskHeap) Peek() *Task {return (*t)[0]
}
相关文章:
Go基于协程池的延迟任务调度器
原理 通过用一个goroutine以及堆来存储要待调度的延迟任务,当达到调度时间后,将其添加到协程池中去执行。 主要是使用了chan、Mutex、atomic及ants协程池来实现。 用途 主要是用于高并发及大量定时任务要处理的情况,如果使用Go协程来实现每…...
k8S通过代理将集群外的中间件引入集群内访问 —— 筑梦之路
背景说明 有部分中间件是跑在Kubernetes集群之外,我们希望通过service的方式来访问集群外的中间件,比如访问我们k8s集群外的elasticsearch集群。 ES节点本身又处在一个负载均衡IP:192.168.100.100 之后,但是代理的端口号是9202&am…...

Linux 第三次脚本作业
源码编译安装httpd 2.4,提供系统服务管理脚本并测试(建议两种方法实现) 一、第一种方法 1、把 httpd-2.4.63.tar.gz 这个安装包上传到你的试验机上 2、 安装编译工具 (俺之前已经装好了) 3、解压httpd包 4、解压后的httpd包的文…...

使用通义万相Wan2.1进行视频生成
使用通义万相Wan2.1进行视频生成 源代码准备运行环境准备创建Python虚拟环境并激活安装依赖包 模型下载生成视频官网的视频生成例子简单描述场景视频生成示例详细描述场景视频生成示例 最近通义万相开源了其视频生成模型。模型有两个版本,一个是1.3B的,一…...
AI技术为旅行社打开新流量入口
2月28日消息,在“2025旅业发展高峰论坛”上,马蜂窝交易中心总经理绳志成在主题演讲中系统性阐述了AI技术对自由行市场的颠覆性影响。 绳志成介绍,传统“大而全”的跟团游产品吸引力持续走低,用户更愿意为“小众秘境”、“在地文化…...

SuperMap iClient3D for WebGL 影像数据可视范围控制
在共享同一影像底图的服务场景中,如何基于用户权限体系实现差异化的数据可视范围控制?SuperMap iClient3D for WebGL提供了自定义区域影像裁剪的方法。让我们一起看看吧! 一、数据制作 对于上述视频中的地图制作,此处不做讲述&am…...
API网关相关知识点
目录 API网关基础知识总结 | JavaGuide Spring Cloud Gateway常见问题总结 | JavaGuide API网关 | 小傅哥 bugstack 虫洞栈 美团: 百亿规模API网关服务Shepherd的设计与实现 vivo: 微服务 API 网关架构实践 唯品会: 高吞吐消息网关的探索与思考 API网关基础知识总结 | J…...

Opencv 图像形态学操作
3.1 形态学-腐蚀操作 img cv2.imread(CSDN.png) cv2.imshow(CSDN, img) cv2.waitKey(0) cv2.destroyAllWindows如果腐蚀核的覆盖区域内的所有像素值都满足条件(阈值),则中心像素的值保持不变;如果有任何像素值不满足条件&#x…...

Readability.js 与 Newspaper提取网页内容和元数据
在当今信息爆炸的时代,网页内容的提取和处理变得尤为重要。无论是从新闻网站、博客还是教程网站中提取内容,都需要一个高效、准确的工具来帮助我们去除无关信息,提取出有价值的正文内容。这不仅能够提高我们的工作效率,还能让我们…...

小程序Three Dof识别 实现景区AR体验
代码工程 GitCode - 全球开发者的开源社区,开源代码托管平台 dof...

腾讯2025年软件测试面试题
以下是基于腾讯等一线互联网公司软件测试岗位的面试趋势和技术要求,025年出现的软件测试面试题。这些问题涵盖了基础知识、自动化测试、性能测试、安全测试、编程能力等多个方面,供参考和准备。 一、基础知识 软件测试的基本概念...

SSL域名证书怎么续期?
在当今数字化时代,网站的安全性已成为企业和个人不可忽视的重要因素。SSL域名证书作为保障网站数据传输安全的关键工具,其重要性不言而喻。然而,SSL证书并非永久有效,它们通常有一个固定的有效期,到期后需要进行续期以…...

Grok3使用体验与模型版本对比分析
文章目录 Grok的功能DeepSearch思考功能绘画功能Grok 3的独特功能 Grok 3的版本和特点与其他AI模型的比较 最新新闻:Grok3被誉为“地球上最聪明的AI” 最近,xAI公司正式发布了Grok3,并宣称其在多项基准测试中展现了惊艳的表现。据官方消息&am…...

《算法宝典:全类型题目索引》
目录 🌴递归、搜索与回溯 一、递归 二、二叉树中的深搜 三、穷举vs暴搜vs深搜vs回溯vs剪枝 四、综合练习 五、FloodFill 算法 六、记忆化搜索 🌵优选算法 一、双指针 二、滑动窗口 三、二分查找 四、前缀和 五、位运算 六、模拟 七、分治 …...

Windows 11 部署 GPUStack 运行 DeepSeek
1. 介绍 DeepSeek 是一个强大的深度学习框架,适用于图像识别、自然语言处理等任务。GPUStack 是一个高效的 GPU 资源管理工具,能够帮助用户更好地利用 GPU 资源进行深度学习任务。本文将详细介绍如何在 Windows 11 系统上部署 GPUStack 并运行 DeepSeek…...
LangChain教程 - RAG - PDF问答
系列文章索引 LangChain教程 - 系列文章 在现代自然语言处理(NLP)中,基于文档内容的问答系统变得愈发重要,尤其是当我们需要从大量文档中提取信息时。通过结合文档检索和生成模型(如RAG,Retrieval-Augment…...
Windows 图形显示驱动开发-WDDM 3.2-自动显示切换(十二)
API 更改 ADS 功能增加了以下公共 API 功能: 枚举系统中的多路复用器设备。查询有关多路复用器的信息,例如,它连接了哪些目标,以及当前切换到哪个目标。触发多路复用器切换。如何检测多路复用器是否已切换。 枚举系统中的多路复…...

《当齐天大圣踏入3A游戏世界:黑神话·悟空的破壁传奇》:此文为AI自动生成
国产 3A 游戏的破晓之光 2024 年 8 月 20 日,这一天注定被铭记在中国游戏发展的史册上。国产首款 3A 游戏《黑神话・悟空》震撼上线,犹如一颗重磅炸弹,在全球游戏市场掀起了惊涛骇浪。仅仅上线 3 小时,其同时在线人数便突破了 140 万,一举打破 Steam 纯单机游戏最高在线纪…...

Graphics View画一个可调速的风机(pyqt)
效果如图: 风机具备调节转速的功能,转速通过扇叶旋转的快慢来区别,共分为四档,其中零档为静止状态,而一、二、三档则依次增加转速。在代码中,BlowerWrapper 类包含了可旋转的扇叶、风机外框以及选项三个主要…...

基于django图书信息管理系统的搭建(增删改查)
✍django项目搭建教程 ☞ ----------------- 教程 本文主要讲解django如何连接数据库MySQL并且可视化展示,实现增删改查功能 目录 一. 创建django应用 二. 数据库配置 三. 查看数据库 四. 编写代码 4.1视图函数 4.2 配置URL 4.3创建模板文件 4.…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...