Go語(yǔ)言并發(fā)定時(shí)任務(wù)之從Sleep到Context的8種寫(xiě)法全解析
本文從 0 開(kāi)始,帶你用一個(gè)超簡(jiǎn)單的任務(wù)(每秒打印一個(gè)數(shù)字)學(xué)會(huì):
- Go
time包的幾種定時(shí) API goroutine、WaitGroup、channel、select、context的用法- 哪些并發(fā)寫(xiě)法是坑,哪些是生產(chǎn)推薦
- 完整可運(yùn)行的
main.go
背景
任務(wù):每秒打印一個(gè)數(shù)字,連續(xù) 10 次。
隱含要求:
- 間隔穩(wěn)定(大約 1 秒)
- 順序正確(1 → 10)
- 最好可以提前取消
為什么這個(gè)任務(wù)重要?
這個(gè)簡(jiǎn)單任務(wù)涵蓋了并發(fā)編程的核心要素:定時(shí)控制、順序保證和生命周期管理。掌握它,你就掌握了 Go 并發(fā)的基礎(chǔ)!
1. 基礎(chǔ)寫(xiě)法:time.Sleep
func UseTimeSleep() {
for i := 1; i <= 10; i++ {
// 暫停當(dāng)前goroutine的執(zhí)行1秒鐘
// time.Second是預(yù)定義的Duration常量,等于1,000,000,000納秒
time.Sleep(time.Second)
// 打印當(dāng)前數(shù)字
fmt.Println(i)
}
}
關(guān)鍵 API 詳解:
time.Sleep(d Duration):暫停當(dāng)前 goroutine 的執(zhí)行至少 d 時(shí)長(zhǎng)time.Second:預(yù)定義的 Duration 常量,表示 1 秒(1e9 納秒)fmt.Println:標(biāo)準(zhǔn)輸出函數(shù),線(xiàn)程安全
優(yōu)點(diǎn):
- 順序正確
- 最簡(jiǎn)單易懂
缺點(diǎn):
- 無(wú)法中途取消
- 實(shí)際間隔 = 1 秒 + 打印耗時(shí),會(huì)有累積誤差
- 阻塞當(dāng)前 goroutine,無(wú)法同時(shí)執(zhí)行其他任務(wù)
適用場(chǎng)景:簡(jiǎn)單腳本、不需要取消功能的短期任務(wù)
2.time.After:一次定時(shí)一次信號(hào)
func UseTimeAfter() {
for i := 1; i <= 10; i++ {
// time.After返回一個(gè)單向接收通道(<-chan Time)
// 該通道會(huì)在指定時(shí)間后發(fā)送一個(gè)時(shí)間值
timerChannel := time.After(time.Second)
// <-操作符會(huì)阻塞,直到通道發(fā)送值
<-timerChannel
fmt.Println(i)
}
}
關(guān)鍵點(diǎn)解析:
time.After(d Duration) <-chan Time:返回一個(gè)只接收通道- 通道操作
<-ch是阻塞操作,直到有數(shù)據(jù)可讀 - 每次循環(huán)都創(chuàng)建新 Timer 對(duì)象,有 GC 壓力
潛在問(wèn)題:
- 頻繁創(chuàng)建 Timer 對(duì)象,可能引起 GC 壓力
- 無(wú)法復(fù)用定時(shí)器
- 同樣無(wú)法取消
適用場(chǎng)景:?jiǎn)未纬瑫r(shí)控制,如網(wǎng)絡(luò)請(qǐng)求超時(shí)
3.time.NewTicker:復(fù)用定時(shí)器
func UseTimeTicker() {
// 創(chuàng)建Ticker對(duì)象,每1秒向C通道發(fā)送當(dāng)前時(shí)間
ticker := time.NewTicker(time.Second)
// defer確保函數(shù)退出時(shí)停止Ticker,釋放資源
defer ticker.Stop()
for i := 1; i <= 10; i++ {
// 從Ticker的C通道讀取值(阻塞直到時(shí)間到)
<-ticker.C
fmt.Println(i)
}
}
Ticker 對(duì)象解析:
time.NewTicker(d Duration) *Ticker:創(chuàng)建周期性定時(shí)器ticker.C <-chan Time:定時(shí)觸發(fā)的通道ticker.Stop():停止定時(shí)器,必須調(diào)用否則資源泄漏
優(yōu)點(diǎn):
- 單一定時(shí)器復(fù)用,高效
- 間隔精確
注意事項(xiàng):
- 忘記 Stop()會(huì)導(dǎo)致 goroutine 泄漏
- 使用后應(yīng)立即 defer Stop()
適用場(chǎng)景:周期性任務(wù),如定時(shí)數(shù)據(jù)采集
4. 并發(fā)誤區(qū)
4.1channel并發(fā)版(順序混亂)
func UseChannel() {
// 創(chuàng)建無(wú)緩沖通道,發(fā)送和接收會(huì)同步阻塞
ch := make(chan int)
for i := 1; i <= 10; i++ {
// 啟動(dòng)goroutine(輕量級(jí)線(xiàn)程)
go func(num int) {
// 每個(gè)goroutine等待不同時(shí)間
time.Sleep(time.Second * time.Duration(num))
// 向通道發(fā)送數(shù)字
ch <- num
}(i) // 注意:必須傳入i的副本,避免閉包捕獲問(wèn)題
}
for i := 1; i <= 10; i++ {
// 從通道接收值(阻塞直到有數(shù)據(jù))
value := <-ch
fmt.Println(value)
}
}
執(zhí)行順序解析:

問(wèn)題分析:
- 10 個(gè) goroutine 并發(fā)執(zhí)行,完成順序不確定
- 通道接收順序 = goroutine 完成順序 ≠ 數(shù)字順序
- 間隔時(shí)間不固定(1 秒到 10 秒)
正確使用場(chǎng)景:獨(dú)立任務(wù)并行處理,如批量圖片處理
4.2WaitGroup并發(fā)版(順序混亂)
func UseGoroutine() {
// 創(chuàng)建WaitGroup用于等待所有g(shù)oroutine完成
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
// 增加等待計(jì)數(shù)
wg.Add(1)
go func(num int) {
// 函數(shù)退出時(shí)減少計(jì)數(shù)
defer wg.Done()
time.Sleep(time.Second * time.Duration(num))
fmt.Println(num)
}(i)
}
// 阻塞直到所有g(shù)oroutine完成
wg.Wait()
}
WaitGroup 原理:
Add(delta int):增加等待計(jì)數(shù)Done():減少計(jì)數(shù)(等價(jià)于 Add(-1))Wait():阻塞直到計(jì)數(shù)歸零
問(wèn)題分析:
- 輸出順序完全隨機(jī)
- 間隔時(shí)間不固定
- 多個(gè) goroutine 同時(shí)調(diào)用 fmt.Println 可能輸出交錯(cuò)
適用場(chǎng)景:并行執(zhí)行獨(dú)立任務(wù),不需要順序保證
5. 正確的并發(fā)寫(xiě)法
5.1 單 goroutine + WaitGroup
func UseSingleGoroutine() {
var wg sync.WaitGroup
// 只需等待1個(gè)goroutine
wg.Add(1)
// 啟動(dòng)工作goroutine
go func() {
// 確保結(jié)束時(shí)通知WaitGroup
defer wg.Done()
for i := 1; i <= 10; i++ {
fmt.Println(i)
time.Sleep(time.Second)
}
}()
// 主goroutine等待工作完成
wg.Wait()
}
架構(gòu)解析:
主goroutine 工作goroutine
│ │
│── wg.Add(1) ────>?│
│ │
│ ├─ 執(zhí)行循環(huán)
│ │ 打印+等待
│ │
│?─── wg.Wait() ─────┤
▼ ▼
優(yōu)點(diǎn):
- 順序和間隔完全可控
- 結(jié)構(gòu)清晰
- 為添加取消功能留出空間
適用場(chǎng)景:需要順序執(zhí)行的定時(shí)任務(wù)
5.2select+Ticker
func UseSelectAndTicker() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for i := 1; i <= 10; i++ {
// select監(jiān)控多個(gè)通道操作
select {
// 當(dāng)ticker.C有值時(shí)執(zhí)行
case <-ticker.C:
fmt.Println(i)
}
}
}
select 關(guān)鍵字詳解:
- 用于監(jiān)聽(tīng)多個(gè)通道操作
- 當(dāng)任意 case 可執(zhí)行時(shí),隨機(jī)選擇一個(gè)執(zhí)行
- 無(wú) default 時(shí)會(huì)阻塞
- 常用于多路復(fù)用
進(jìn)階用法(添加退出通道):
quit := make(chan struct{})
// ...
select {
case <-ticker.C:
fmt.Println(i)
case <-quit:
fmt.Println("提前退出")
return
}
適用場(chǎng)景:需要監(jiān)控多個(gè)事件源的定時(shí)任務(wù)
5.3context+Ticker(生產(chǎn)推薦)
func UseContextWithTicker() {
// 創(chuàng)建可取消的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: // 定時(shí)觸發(fā)
fmt.Println(i)
i++
if i > 10 {
return // 自然結(jié)束
}
case <-ctx.Done(): // 上下文取消
fmt.Println("任務(wù)取消:", ctx.Err())
return
}
}
}
context 包深度解析:

核心優(yōu)勢(shì):
- 取消傳播:一次取消,所有監(jiān)聽(tīng)組件都能收到通知
- 超時(shí)控制:可添加 WithTimeout 自動(dòng)取消
- 資源安全:defer 確保資源釋放
- 標(biāo)準(zhǔn)統(tǒng)一:Go 標(biāo)準(zhǔn)庫(kù)廣泛使用
實(shí)際應(yīng)用:
// 帶超時(shí)的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 在數(shù)據(jù)庫(kù)查詢(xún)中使用 err := db.QueryContext(ctx, "SELECT...")
適用場(chǎng)景:所有生產(chǎn)環(huán)境需要生命周期管理的并發(fā)任務(wù)
6. 方法對(duì)比
| 方案 | 順序 | 間隔 | 可取消 | 資源回收 | 復(fù)雜度 | 適用場(chǎng)景 |
|---|---|---|---|---|---|---|
| Sleep | ? | 一般 | ? | ? | ? | 簡(jiǎn)單腳本 |
| After | ? | ? | ? | ? | ? | 單次超時(shí) |
| Ticker | ? | ? | ? | ? | ?? | 周期性任務(wù) |
| Channel 并發(fā) | ? | ? | ? | ? | ??? | 并行獨(dú)立任務(wù)(不要求順序) |
| WaitGroup 并發(fā) | ? | ? | ? | ? | ?? | 并行獨(dú)立任務(wù)(簡(jiǎn)單同步) |
| 單 goroutine+WG | ? | ? | ? | ? | ?? | 順序定時(shí)任務(wù) |
| select+Ticker | ? | ? | ??* | ? | ??? | 多事件源監(jiān)控 |
| context+Ticker | ? | ? | ? | ? | ???? | 生產(chǎn)級(jí)定時(shí)任務(wù)(推薦) |
*需自行實(shí)現(xiàn)取消通道
7. 完整可運(yùn)行示例
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 所有實(shí)現(xiàn)函數(shù)(前面已詳細(xì)解釋?zhuān)?
func main() {
fmt.Println("=== 基礎(chǔ): time.Sleep ===")
UseTimeSleep()
fmt.Println("\n=== 基礎(chǔ): time.After ===")
UseTimeAfter()
fmt.Println("\n=== 基礎(chǔ): time.Ticker ===")
UseTimeTicker()
fmt.Println("\n=== 誤區(qū): Channel并發(fā) ===")
UseChannel()
fmt.Println("\n=== 誤區(qū): WaitGroup并發(fā) ===")
UseGoroutine()
fmt.Println("\n=== 正確: 單goroutine+WaitGroup ===")
UseSingleGoroutine()
fmt.Println("\n=== 正確: select+Ticker ===")
UseSelectAndTicker()
fmt.Println("\n=== 生產(chǎn)推薦: context+Ticker ===")
UseContextWithTicker()
fmt.Println("\n=== 所有示例執(zhí)行完成 ===")
}
運(yùn)行方式:
# 運(yùn)行程序
go run main.go
# 輸出示例:
=== 基礎(chǔ): time.Sleep ===
1
2
...
10
=== 誤區(qū): Channel并發(fā) ===
1
3
2
... # 順序隨機(jī)
進(jìn)階學(xué)習(xí)建議
context 的更多用法:
context.WithTimeout:自動(dòng)超時(shí)取消context.WithValue:傳遞請(qǐng)求范圍數(shù)據(jù)- 上下文傳遞規(guī)范
錯(cuò)誤處理模式:
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errCh:
return err
}
資源管理最佳實(shí)踐:
- 總是 defer 關(guān)閉資源
- 使用
sync.Once確保單次初始化 - 避免在循環(huán)中創(chuàng)建 goroutine
性能調(diào)優(yōu):
- 使用
pprof分析 goroutine 泄漏 - 合理設(shè)置 GOMAXPROCS
- 避免過(guò)度并發(fā)
實(shí)際項(xiàng)目中的并發(fā)往往更復(fù)雜,但核心原理不變。掌握這些基礎(chǔ)模式,你就能構(gòu)建健壯的并發(fā)系統(tǒng)!
到此這篇關(guān)于Go語(yǔ)言并發(fā)定時(shí)任務(wù)之從Sleep到Context的8種寫(xiě)法全解析的文章就介紹到這了,更多相關(guān)Go語(yǔ)言并發(fā)定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go-Web框架中AOP方案的實(shí)現(xiàn)方式
本文主要介紹了Go-Web框架中AOP方案的實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
使用Go語(yǔ)言解決Scan空格結(jié)束輸入問(wèn)題
這篇文章主要為大家介紹了使用Go語(yǔ)言來(lái)解決Scan空格結(jié)束輸入問(wèn)題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
使用go語(yǔ)言實(shí)現(xiàn)Redis持久化的示例代碼
redis 是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),如果你把進(jìn)程殺掉,那么里面存儲(chǔ)的數(shù)據(jù)都會(huì)消失,那么這篇文章就是來(lái)解決 redis 持久化的問(wèn)題,本文給大家介紹了使用go語(yǔ)言實(shí)現(xiàn)Redis持久化,需要的朋友可以參考下2024-07-07
Golang基于Vault實(shí)現(xiàn)敏感數(shù)據(jù)加解密
數(shù)據(jù)加密是主要的數(shù)據(jù)安全防護(hù)技術(shù)之一,敏感數(shù)據(jù)應(yīng)該加密存儲(chǔ)在數(shù)據(jù)庫(kù)中,降低泄露風(fēng)險(xiǎn),本文將介紹一下利用Vault實(shí)現(xiàn)敏感數(shù)據(jù)加解密的方法,需要的可以參考一下2023-07-07
gorm FirstOrCreate和受影響的行數(shù)實(shí)例
這篇文章主要介紹了gorm FirstOrCreate和受影響的行數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解
這篇文章主要為大家介紹了Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

