Go語言并發(fā)定時(shí)任務(wù)之從Sleep到Context的8種寫法全解析
本文從 0 開始,帶你用一個(gè)超簡(jiǎn)單的任務(wù)(每秒打印一個(gè)數(shù)字)學(xué)會(huì):
- Go
time
包的幾種定時(shí) API goroutine
、WaitGroup
、channel
、select
、context
的用法- 哪些并發(fā)寫法是坑,哪些是生產(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ǔ)寫法: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ù),線程安全
優(yōu)點(diǎn):
- 順序正確
- 最簡(jiǎn)單易懂
缺點(diǎn):
- 無法中途取消
- 實(shí)際間隔 = 1 秒 + 打印耗時(shí),會(huì)有累積誤差
- 阻塞當(dāng)前 goroutine,無法同時(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 壓力
潛在問題:
- 頻繁創(chuàng)建 Timer 對(duì)象,可能引起 GC 壓力
- 無法復(fù)用定時(shí)器
- 同樣無法取消
適用場(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)建無緩沖通道,發(fā)送和接收會(huì)同步阻塞 ch := make(chan int) for i := 1; i <= 10; i++ { // 啟動(dòng)goroutine(輕量級(jí)線程) go func(num int) { // 每個(gè)goroutine等待不同時(shí)間 time.Sleep(time.Second * time.Duration(num)) // 向通道發(fā)送數(shù)字 ch <- num }(i) // 注意:必須傳入i的副本,避免閉包捕獲問題 } for i := 1; i <= 10; i++ { // 從通道接收值(阻塞直到有數(shù)據(jù)) value := <-ch fmt.Println(value) } }
執(zhí)行順序解析:
問題分析:
- 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ù)歸零
問題分析:
- 輸出順序完全隨機(jī)
- 間隔時(shí)間不固定
- 多個(gè) goroutine 同時(shí)調(diào)用 fmt.Println 可能輸出交錯(cuò)
適用場(chǎng)景:并行執(zhí)行獨(dú)立任務(wù),不需要順序保證
5. 正確的并發(fā)寫法
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)聽多個(gè)通道操作
- 當(dāng)任意 case 可執(zhí)行時(shí),隨機(jī)選擇一個(gè)執(zhí)行
- 無 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)聽組件都能收到通知
- 超時(shí)控制:可添加 WithTimeout 自動(dòng)取消
- 資源安全:defer 確保資源釋放
- 標(biāo)準(zhǔn)統(tǒng)一:Go 標(biāo)準(zhǔn)庫廣泛使用
實(shí)際應(yīng)用:
// 帶超時(shí)的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 在數(shù)據(jù)庫查詢中使用 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ì)解釋) 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
- 避免過度并發(fā)
實(shí)際項(xiàng)目中的并發(fā)往往更復(fù)雜,但核心原理不變。掌握這些基礎(chǔ)模式,你就能構(gòu)建健壯的并發(fā)系統(tǒng)!
到此這篇關(guān)于Go語言并發(fā)定時(shí)任務(wù)之從Sleep到Context的8種寫法全解析的文章就介紹到這了,更多相關(guān)Go語言并發(fā)定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go-Web框架中AOP方案的實(shí)現(xiàn)方式
本文主要介紹了Go-Web框架中AOP方案的實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06使用go語言實(shí)現(xiàn)Redis持久化的示例代碼
redis 是一個(gè)內(nèi)存數(shù)據(jù)庫,如果你把進(jìn)程殺掉,那么里面存儲(chǔ)的數(shù)據(jù)都會(huì)消失,那么這篇文章就是來解決 redis 持久化的問題,本文給大家介紹了使用go語言實(shí)現(xiàn)Redis持久化,需要的朋友可以參考下2024-07-07Golang基于Vault實(shí)現(xiàn)敏感數(shù)據(jù)加解密
數(shù)據(jù)加密是主要的數(shù)據(jù)安全防護(hù)技術(shù)之一,敏感數(shù)據(jù)應(yīng)該加密存儲(chǔ)在數(shù)據(jù)庫中,降低泄露風(fēng)險(xiǎn),本文將介紹一下利用Vault實(shí)現(xiàn)敏感數(shù)據(jù)加解密的方法,需要的可以參考一下2023-07-07gorm FirstOrCreate和受影響的行數(shù)實(shí)例
這篇文章主要介紹了gorm FirstOrCreate和受影響的行數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解
這篇文章主要為大家介紹了Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06