【Go】使用Go语言打造定时提醒小工具,从基础到优化全方位探索

news2025/2/25 14:10:10

文章目录

    • 一、引言
      • 1.目的和背景
      • 2.选择GO语言的原因
    • 二、GO语言中的时间和定时器
      • 1.时间相关的包和函数
      • 2.定时器相关的包和函数
    • 三、使用GO语言实现功能
    • 四、代码改进
      • 1.time.AfterFunc()
      • 2.sync.WaitGroup
      • 3.接收参数
    • 五、总结

一、引言

1.目的和背景

本文为征文活动“CSDN 征文活动:和日期相关的代码和bug (2/21 - 2/28)”的投稿文章,目的是和大家分享编程语言对于时间和日期的处理方式,以及代码的优化思路。

2.选择GO语言的原因

  1. 简单易学:GO语言的语法简单易学,这使得初学者能够快速上手,减少学习成本。
  2. 高效性能:GO语言的编译器可以将代码转换成本地机器码,因此它可以提供出色的性能和响应速度。
  3. 并发支持:GO语言的并发模型使得开发者可以轻松地编写高并发应用程序,而无需关注底层细节。
  4. 开源社区:GO语言拥有一个强大的开源社区,因此可以使用各种可用的库和工具来帮助开发人员快速完成任务。
  5. 跨平台支持:GO语言可以跨多个操作系统和硬件架构运行,因此它可以用于开发多种类型的应用程序。

二、GO语言中的时间和定时器

1.时间相关的包和函数

GO语言中有两个与时间相关的包:time和date。time包提供了许多有用的函数和类型来处理时间,包括时间的解析、格式化和计算等。date包则提供了一些与日期相关的函数和类型,例如日期的解析和格式化等。

下面是一些常用的time包函数和类型:

  • time.Now():返回当前本地时间。
  • time.Date():返回一个指定日期和时间的Time类型。
  • time.Sleep():休眠指定的时间。
  • time.Tick():返回一个定时的通道,每隔一段时间发送一个时间值。
  • time.After():返回一个通道,指定时间后发送一个时间值。

除了time包,GO语言还提供了一个time.Timer类型,用于定时器的管理。Timer类型提供了Reset()和Stop()方法,用于重新设置定时器和停止定时器。

2.定时器相关的包和函数

GO语言中的time包提供了一个Ticker类型和一个Timer类型,用于定时器的管理。Ticker类型可以按照一定的时间间隔循环触发事件,而Timer类型可以在指定时间后触发事件。

下面是一些常用的Ticker和Timer相关的函数和类型:

  • time.NewTicker():创建一个Ticker类型的定时器。
  • time.Ticker.C:返回一个定时的通道,每隔一段时间发送一个时间值。
  • time.Ticker.Stop():停止定时器。
  • time.NewTimer():创建一个Timer类型的定时器。
  • time.Timer.C:返回一个通道,定时器到期后发送一个时间值。
  • time.Timer.Reset():重新设置定时器到期时间。
  • time.Timer.Stop():停止定时器。

我们可以使用这些函数和类型来实现一个简单的定时器,例如在20分钟后触发提醒事件。

三、使用GO语言实现功能

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(20 * time.Minute) // 创建一个20分钟的定时器
	<-timer.C // 等待定时器到期
	fmt.Println("时间到!请注意您的时间管理。")
}

在上面的代码中,我们使用time.NewTimer()函数创建了一个20分钟的定时器。然后,我们通过从定时器的通道timer.C中读取数据,来等待定时器到期。一旦定时器到期,我们就会收到一个数据,然后在控制台中打印一条提醒信息。

我们在本地编译之后直接执行,为了方便测试,我把时间改成了1分钟。

image-20230222104933297

四、代码改进

1.time.AfterFunc()

当使用 time.NewTimer() 函数创建一个定时器后,程序会阻塞在 <-timer.C 语句处等待定时器到期。如果我们想在等待定时器到期的同时,可以执行其他操作或等待多个定时器同时到期,就需要使用 time.AfterFunc() 函数。

time.AfterFunc() 函数可以设置一个定时器并在定时器到期时,执行指定的回调函数。下面是使用 time.AfterFunc() 函数实现定时器的示例代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	duration := 20 * time.Minute // 定时器时长为20分钟
	timer := time.AfterFunc(duration, func() {
		fmt.Println("时间到了!20分钟已经过去了。")
	})
	defer timer.Stop() // 停止定时器,防止回调函数未执行时程序提前退出
	fmt.Printf("定时器已经设置,等待 %v 后提醒...\n", duration)
	time.Sleep(30 * time.Minute) // 等待30分钟,以便看到定时器的效果
}

我们使用 time.AfterFunc() 函数设置了一个 20 分钟的定时器,并在定时器到期时执行指定的回调函数。与使用 time.NewTimer() 不同,time.AfterFunc() 函数并不会阻塞程序的执行。因此,我们需要使用 time.Sleep() 函数等待一段时间,以便观察定时器的效果。

2.sync.WaitGroup

使用 time.Sleep() 等待定时器到期的方式不够优雅,因为它将阻塞程序的执行。我们可以使用 sync.WaitGroup 来等待定时器到期,并避免阻塞程序的执行。

sync.WaitGroup 是 Go 语言提供的一种同步原语,它允许我们等待一组协程完成它们的任务。在我们的例子中,我们可以使用 WaitGroup 来等待定时器到期。具体来说,我们可以创建一个 WaitGroup,然后在启动协程之前调用 Add(1) 方法来增加计数器的值,表示有一个协程需要等待。然后在协程中等待定时器到期,并在定时器到期后调用 Done() 方法,将计数器减一。最后,我们可以使用 Wait() 方法来等待计数器的值减少到零,表示所有协程都已经完成它们的任务。

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	fmt.Println("定时器已经设置,等待 20m0s 后提醒...")
	var wg sync.WaitGroup
	wg.Add(1)
	time.AfterFunc(20*time.Minute, func() {
		fmt.Println("时间到了!20分钟已经过去了。")
		wg.Done()
	})
	wg.Wait()
}

3.接收参数

通过以下方式实现定时器的暂停、继续和关闭:

  1. 暂停:使用 timer.Stop() 方法停止定时器。可以在定时器到期前任意时间内调用该方法,以暂停定时器。
  2. 继续:使用 time.NewTimer() 方法重新创建一个定时器,并传递剩余时间作为参数。例如,如果之前创建的定时器是 20 分钟的,而它已经运行了 10 分钟,那么可以重新创建一个定时器,时间为 10 分钟,来继续计时。
  3. 关闭:使用 os.Exit() 方法退出程序。
package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var timer *time.Timer
var isPaused bool

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 输入提醒时间
	fmt.Println("请输入提醒时间(单位:分钟):")
	durationStr, _ := reader.ReadString('\n')
	duration, _ := strconv.Atoi(strings.TrimSpace(durationStr))
	durationDuration := time.Duration(duration) * time.Minute

	// 创建定时器
	timer = time.NewTimer(durationDuration)
	fmt.Printf("定时器已经设置为 %d 分钟\n", duration)

	// 开始提醒
	go func() {
		for {
			if isPaused {
				continue
			}

			<-timer.C
			sendNotification("时间到了,休息一下吧")
		}
	}()

	// 监听命令行输入
	for {
		commandStr, _ := reader.ReadString('\n')
		command := strings.TrimSpace(commandStr)

		switch command {
		case "pause":
			isPaused = true
			timer.Stop()
			fmt.Println("提醒已暂停")
		case "resume":
			isPaused = false
			timer.Reset(durationDuration)
			fmt.Println("提醒已恢复")
		case "quit":
			fmt.Println("程序已退出")
			return
		default:
			fmt.Println("请输入正确的指令")
		}
	}
}

// 发送通知
func sendNotification(message string) {
	cmd := exec.Command("osascript", "-e", fmt.Sprintf(`display notification "%s" with title "休息提醒"`, message))
	_ = cmd.Run()
	fmt.Printf("【提醒】%s\n", message)
}

执行效果如下图所示。

image-20230222113432645

五、总结

好了,这个定时器我们就先写到这里,毕竟,代码优化是永远做不完的。

从需求出发,通过选择 Go 语言和定时器相关的包和函数,实现了最初版本的定时提醒功能。接着,为了优化代码和提醒方式,引入了系统通知,并且使用命令行接收用户指令,实现了暂停、继续和关闭功能。

在实现过程中,我们采用了基于 Go 语言标准库中的 time 包的定时器实现定时提醒的功能。为了优化用户体验,我们使用命令行接收用户指令。

在优化过程中,我们引入了 WaitGroup 来确保所有 goroutine 的同步,同时将定时器的重置和取消的代码进行了封装。此外,我们还对代码进行了错误处理,保证程序的健壮性。

通过不断迭代和优化,我们最终实现了完整的定时提醒功能,提高了用户体验和代码的可维护性。

"Learn from yesterday, live for today, hope for tomorrow. The important thing is not to stop questioning." - Albert Einstein

"从昨天中学习,活在今天,寄望于明天。重要的是不要停止探索。" - 阿尔伯特·爱因斯坦

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(二十二)、实现评论功能(2)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;渲染评论列表 1.1&#xff0c;在detail页面中定义评论列表数组和getcomment方法&#xff1a; commentList: [],getcomment方法&#xff1a; //获取评论列表async getComment() {let commentTemp db.collection("quanzi_comment").where(article_id …

浏览器跨域问题

跨域问题什么是跨域问题如何解决跨域问题JSONPCORS方式解决跨域使用 Nginx 反向代理使用 WebSocket跨源请求是否能携带Cookie什么是跨域问题 跨域问题指的是不同站点之间&#xff0c;使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制&#xff0c;它的初衷是为…

【离线数仓-3-数仓建模方法理论汇总】

离线数仓-3-数仓建模方法理论汇总离线数仓-3-数仓建模方法理论汇总1.数仓概述2.数据仓库核心架构&#xff08;Hive&#xff09;3.数据仓库建模概述4.数据仓库建模方法论1.ER&#xff08;Entity Relationship&#xff09;模型2.维度模型1.维度建模理论-事实表1. 事实表概述2.事实…

RabbitMQ学习(十):发布确认高级

一、概述在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败导致消息丢失&#xff0c;需要手动处理和恢复。在这样比较极端的情况&#xff0c;当RabbitMQ 集群不可用的时候&#xff0c;无法投递的消息该如何处理呢…

面试题:HashMap为什么是线程不安全的?解决办法是什么?

在JDK1.7中容易造成死循环和数据丢失&#xff0c;造成的原因如下图假设某个时刻t1,t2都访问到了链表&#xff0c;t1,t2的下一个节点都是b,如图此时内存耗尽&#xff0c;线程t2线程进入等待状态&#xff0c;假设此时刚好达到临界点需要扩容&#xff0c;t1进行扩容&#xff0c;并…

【20230210】二叉树小结

二叉树的种类二叉树的主要形式&#xff1a;满二叉树和完全二叉树。满二叉树深度为k&#xff0c;有2^k-1个节点的二叉树完全二叉树除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。二叉搜索树…

浅谈毫米波技术与应用

浅谈毫米波之技术篇2020年10月GSMA发布的《5G毫米波技术白皮书》预计&#xff0c;在2022年北京冬奥会上&#xff0c;5G毫米波有望大放异彩&#xff0c;为观众、媒体转播者、赛事组织和参与者等提供优质的观赛体验、完备的服务保障&#xff0c;将可提供全景VR、新型信息交互、智…

SCADA-1-组态前期需求调研篇

近期有朋友找到我&#xff0c;说scada组态系统开源的很少&#xff0c;不少开发者借此售卖这种软件&#xff0c;我回了句&#xff1a;这有什么难的&#xff0c;不就是拖拖拽拽&#xff0c;再绑定上数据源&#xff0c;实现动态效果嘛。。。&#xff08;先装了个X&#xff09;一、…

Web前端:全栈开发人员的责任

多年来&#xff0c;关于全栈开发人员有很多说法&#xff0c;全栈开发人员是一位精通应用程序全栈开发过程的专业人士。这包括数据库、API、前端技术、后端开发语言和控制系统版本。你一定遇到过前端和后端开发人员。前端开发人员将构建接口&#xff0c;而后端开发人员将开发、更…

使用 Xcode 创建第一个 Objective-C 命令行程序 HelloWorld

总目录 iOS开发笔记目录 从一无所知到入门 文章目录创建项目运行项目&#xff0c;查看日志输出同一项目下新增子目录&#xff0c;切换要运行的 Target创建项目 打开 Xcode &#xff0c;Create a new Xcode project 接下来的默认界面&#xff1a; 切换到 macOS 下&#xff…

攻击者失手,自己杀死了僵尸网络 KmsdBot

此前&#xff0c;Akamai 的安全研究员披露了 KmsdBot 僵尸网络&#xff0c;该僵尸网络主要通过 SSH 爆破与弱口令进行传播。在对该僵尸网络的持续跟踪中&#xff0c;研究人员发现了一些有趣的事情。 C&C 控制 对恶意活动来说&#xff0c;最致命的就是夺取对 C&C 服务…

后端基础SQL

SQL基础语法: sql对大小写不敏感&#xff0c;eg: SELECT 等效于 select&#xff1b;select: select用于从表中查找数据&#xff0c;select 列名 from 表名 —> 结果集:&#xff1a;仅有查询列的结果表&#xff1b; SELECT * FROM 表名称 ----> 结果集: 查找表的所有数据…

你是客户喜欢的那类外贸业务员吗

某天&#xff0c;一个智利的客户发了一封邮件来&#xff0c;只为了告诉我一个好消息——他的产品进入了 Walmart。01以下是他的原文&#xff1a;Hi Sam,Just for you to know, that 2-3 month ago, We take part in a bidding, and we win with the clip caps.They buy 400-500…

深入浅出C++ ——set类深度剖析

文章目录一、关联式容器二、键值对三、树形结构的关联式容器四、set类介绍六、set的使用七、multiset一、关联式容器 STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性…

如何解决过拟合与欠拟合,及理解k折交叉验证

模型欠拟合&#xff1a;在训练集以及测试集上同时具有较⾼的误差&#xff0c;此时模型的偏差较⼤&#xff1b; 模型过拟合&#xff1a;在训练集上具有较低的误差&#xff0c;在测试集上具有较⾼的误差&#xff0c;此时模型的⽅差较⼤。 如何解决⽋拟合&#xff1a; 添加其他特…

【蓝桥杯集训8】哈希表专题(3 / 3)

目录 手写哈希表 1、开放寻址法 2、拉链法 字符串前缀哈希表法 2058. 笨拙的手指 - 哈希表 秦九韶算法&#xff08;进制转换&#xff09; 枚举 秦九韶算法——将x进制数转化为十进制数 手写哈希表 活动 - AcWing 1、开放寻址法 设 h(x)k&#xff0c;也就是 x 的哈希值…

TPM 2.0实例探索2 —— LUKS磁盘加密(3)

接前文&#xff1a;TPM 2.0实例探索2 —— LUKS磁盘加密&#xff08;2&#xff09; 本文大部分内容参考&#xff1a; Code Sample: Protecting secret data and keys using Intel Platform... 二、LUKS磁盘加密实例 3. 将密码存储于TPM的LUKS 由于自动挂载需要在运行时提供一…

Java中的Stack与Queue

文章目录一、栈的概念及使用1.1 概念1.2 栈的使用1.3 栈的模拟实现二、队列的概念及使用2.1 概念2.2 队列的使用2.3 双端队列(Deque)三、相关OJ题3.1 用队列实现栈。3.2 用栈实现队列。总结一、栈的概念及使用 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在…

【Git】码云

目录 5、 Git 团队协作机制 5.1 团队内协作 5.2 跨团队协作 6、 Gitee码云 操作 6.1 创建远程仓库 6.2 远程仓库操作 6.3 SSH 免密登录 5、 Git 团队协作机制 5.1 团队内协作 5.2 跨团队协作 6、 Gitee码云 操作 码云网址&#xff1a; https://githee.com/ 账号验证…

【0基础学爬虫】爬虫基础之HTTP协议的基本原理介绍

大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0基础学…