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

时间:2023-02-22 11:56:39

一、引言

1.目的和背景

本文和大家分享编程语言对于时间和日期的处理方式,以及代码的优化思路。

2.选择GO语言的原因

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

【Go】使用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分钟。

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

四、代码改进

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)
}

执行效果如下图所示。

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

五、总结

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

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

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

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

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

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

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

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