Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言并发定时任务

Go语言并发定时任务之从Sleep到Context的8种写法全解析

作者:mCell

这篇文章主要为大家详细介绍了Go语言并发定时任务之从Sleep到Context的8种写法的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

本文从 0 开始,带你用一个超简单的任务(每秒打印一个数字)学会:

背景

任务:每秒打印一个数字,连续 10 次。

隐含要求

为什么这个任务重要?

这个简单任务涵盖了并发编程的核心要素:定时控制顺序保证生命周期管理。掌握它,你就掌握了 Go 并发的基础!

1. 基础写法:time.Sleep

func UseTimeSleep() {
    for i := 1; i <= 10; i++ {
        // 暂停当前goroutine的执行1秒钟
        // time.Second是预定义的Duration常量,等于1,000,000,000纳秒
        time.Sleep(time.Second)

        // 打印当前数字
        fmt.Println(i)
    }
}

关键 API 详解

优点

缺点

适用场景:简单脚本、不需要取消功能的短期任务

2.time.After:一次定时一次信号

func UseTimeAfter() {
    for i := 1; i <= 10; i++ {
        // time.After返回一个单向接收通道(<-chan Time)
        // 该通道会在指定时间后发送一个时间值
        timerChannel := time.After(time.Second)

        // <-操作符会阻塞,直到通道发送值
        <-timerChannel

        fmt.Println(i)
    }
}

关键点解析

潜在问题

适用场景:单次超时控制,如网络请求超时

3.time.NewTicker:复用定时器

func UseTimeTicker() {
    // 创建Ticker对象,每1秒向C通道发送当前时间
    ticker := time.NewTicker(time.Second)

    // defer确保函数退出时停止Ticker,释放资源
    defer ticker.Stop()

    for i := 1; i <= 10; i++ {
        // 从Ticker的C通道读取值(阻塞直到时间到)
        <-ticker.C

        fmt.Println(i)
    }
}

Ticker 对象解析

优点

注意事项

适用场景:周期性任务,如定时数据采集

4. 并发误区

4.1channel并发版(顺序混乱)

func UseChannel() {
    // 创建无缓冲通道,发送和接收会同步阻塞
    ch := make(chan int)

    for i := 1; i <= 10; i++ {
        // 启动goroutine(轻量级线程)
        go func(num int) {
            // 每个goroutine等待不同时间
            time.Sleep(time.Second * time.Duration(num))

            // 向通道发送数字
            ch <- num
        }(i) // 注意:必须传入i的副本,避免闭包捕获问题
    }

    for i := 1; i <= 10; i++ {
        // 从通道接收值(阻塞直到有数据)
        value := <-ch
        fmt.Println(value)
    }
}

执行顺序解析

问题分析

正确使用场景:独立任务并行处理,如批量图片处理

4.2WaitGroup并发版(顺序混乱)

func UseGoroutine() {
    // 创建WaitGroup用于等待所有goroutine完成
    var wg sync.WaitGroup

    for i := 1; i <= 10; i++ {
        // 增加等待计数
        wg.Add(1)

        go func(num int) {
            // 函数退出时减少计数
            defer wg.Done()

            time.Sleep(time.Second * time.Duration(num))
            fmt.Println(num)
        }(i)
    }

    // 阻塞直到所有goroutine完成
    wg.Wait()
}

WaitGroup 原理

问题分析

适用场景:并行执行独立任务,不需要顺序保证

5. 正确的并发写法

5.1 单 goroutine + WaitGroup

func UseSingleGoroutine() {
    var wg sync.WaitGroup

    // 只需等待1个goroutine
    wg.Add(1)

    // 启动工作goroutine
    go func() {
        // 确保结束时通知WaitGroup
        defer wg.Done()

        for i := 1; i <= 10; i++ {
            fmt.Println(i)
            time.Sleep(time.Second)
        }
    }()

    // 主goroutine等待工作完成
    wg.Wait()
}

架构解析

主goroutine         工作goroutine
    │                    │
    │── wg.Add(1) ────>▶│
    │                    │
    │                    ├─ 执行循环
    │                    │   打印+等待
    │                    │
    │◀─── wg.Wait() ─────┤
    ▼                    ▼

优点

适用场景:需要顺序执行的定时任务

5.2select+Ticker

func UseSelectAndTicker() {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for i := 1; i <= 10; i++ {
        // select监控多个通道操作
        select {
        // 当ticker.C有值时执行
        case <-ticker.C:
            fmt.Println(i)
        }
    }
}

select 关键字详解

进阶用法(添加退出通道):

quit := make(chan struct{})
// ...
select {
case <-ticker.C:
    fmt.Println(i)
case <-quit:
    fmt.Println("提前退出")
    return
}

适用场景:需要监控多个事件源的定时任务

5.3context+Ticker(生产推荐)

func UseContextWithTicker() {
    // 创建可取消的context
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 确保资源释放

    ticker := time.NewTicker(time.Second)
    defer ticker.Stop() // 确保停止ticker

    i := 1
    for {
        select {
        case <-ticker.C: // 定时触发
            fmt.Println(i)
            i++
            if i > 10 {
                return // 自然结束
            }

        case <-ctx.Done(): // 上下文取消
            fmt.Println("任务取消:", ctx.Err())
            return
        }
    }
}

context 包深度解析

核心优势

实际应用

// 带超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// 在数据库查询中使用
err := db.QueryContext(ctx, "SELECT...")

适用场景:所有生产环境需要生命周期管理的并发任务

6. 方法对比

方案顺序间隔可取消资源回收复杂度适用场景
Sleep一般简单脚本
After单次超时
Ticker⭐⭐周期性任务
Channel 并发⭐⭐⭐并行独立任务(不要求顺序)
WaitGroup 并发⭐⭐并行独立任务(简单同步)
单 goroutine+WG⭐⭐顺序定时任务
select+Ticker⚠️*⭐⭐⭐多事件源监控
context+Ticker⭐⭐⭐⭐生产级定时任务(推荐)

*需自行实现取消通道

7. 完整可运行示例

package main

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

// 所有实现函数(前面已详细解释)

func main() {
    fmt.Println("=== 基础: time.Sleep ===")
    UseTimeSleep()

    fmt.Println("\n=== 基础: time.After ===")
    UseTimeAfter()

    fmt.Println("\n=== 基础: time.Ticker ===")
    UseTimeTicker()

    fmt.Println("\n=== 误区: Channel并发 ===")
    UseChannel()

    fmt.Println("\n=== 误区: WaitGroup并发 ===")
    UseGoroutine()

    fmt.Println("\n=== 正确: 单goroutine+WaitGroup ===")
    UseSingleGoroutine()

    fmt.Println("\n=== 正确: select+Ticker ===")
    UseSelectAndTicker()

    fmt.Println("\n=== 生产推荐: context+Ticker ===")
    UseContextWithTicker()

    fmt.Println("\n=== 所有示例执行完成 ===")
}

运行方式

# 运行程序
go run main.go

# 输出示例:
=== 基础: time.Sleep ===
1
2
...
10

=== 误区: Channel并发 ===
1
3
2
... # 顺序随机

进阶学习建议

context 的更多用法

错误处理模式

select {
case <-ctx.Done():
    return ctx.Err()
case err := <-errCh:
    return err
}

资源管理最佳实践

性能调优

实际项目中的并发往往更复杂,但核心原理不变。掌握这些基础模式,你就能构建健壮的并发系统!

到此这篇关于Go语言并发定时任务之从Sleep到Context的8种写法全解析的文章就介绍到这了,更多相关Go语言并发定时任务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文