支持首次觸發的 Go Ticker

来源:https://www.cnblogs.com/canyuexiang/archive/2022/07/04/16441444.html
-Advertisement-
Play Games

促使我寫這篇文章主要是在寫一個關於虛擬貨幣賬戶監控的項目時使用 Ticker 的問題。 Ticker 的問題 如果用過 Ticker 的朋友會知道,創建 Ticker 後並不會馬上執行,而是會等待一個時間 d,這就是創建時的間隔時間。如果間隔時間很短這基本上不會有太大問題,但是如果對首次執行時間有要 ...


促使我寫這篇文章主要是在寫一個關於虛擬貨幣賬戶監控的項目時使用 Ticker 的問題。

Ticker 的問題

如果用過 Ticker 的朋友會知道,創建 Ticker 後並不會馬上執行,而是會等待一個時間 d,這就是創建時的間隔時間。如果間隔時間很短這基本上不會有太大問題,但是如果對首次執行時間有要求,就會很麻煩。例如以下這個案例:


package main

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

func main() {
	ts := time.NewTicker(5 * time.Second)
	fmt.Println("start_time#", time.Now().Unix())
	chanClose := make(chan struct{})
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case <-chanClose:
				return
			case <-ts.C:
				fmt.Println("run_time#", time.Now().Unix())
			}
		}
	}()

	go func() {
		time.Sleep(10 * time.Second)
		chanClose <- struct{}{}
		ts.Stop()
	}()
	wg.Wait()
}

它將返回以下內容:

start_time# 1656860176
run_time# 1656860181
run_time# 1656860186

為了方便演示我們在事例中設了一個很短的時間,我們可以看到從代碼啟動到真正定時器觸發,代碼等待了5秒,就是time.NewTicker 創建時我們傳的參數時間。但如果我們把這個時間改成1個小時,我們需要等待1個小時才會真正開始執行。

尋找解決方案

在我的項目中需要定時器馬上執行,所以我通過搜索搜到了 Go 官方倉庫 Issues 中提到過這個問題的解決方案 “time: create ticker with instant first tick”。我們可以看一下這個事例:

package main

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

func main() {
	ts := time.NewTicker(5 * time.Second)
	fmt.Println("start_time#", time.Now().Unix())
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		for ; true; <-ts.C {
			fmt.Println("run_time#", time.Now().Unix())
		}
	}()

	go func() {
		time.Sleep(10 * time.Second)
		ts.Stop()
		wg.Done()
	}()
	wg.Wait()
}

上述的執行後返回內容:

start_time# 1656860889
run_time# 1656860889
run_time# 1656860894

我們可以看到首次定時器觸發任務的時間變成了程式執行的開始時間!在我們的例子中,這種方式沒有問題,但是我們需要關註退出條件,在這裡是 main goroutine 直接退出。第一個 goroutine 其實直到 main 退出前一直是堵塞狀態。如果你的項目中多次使用這種形式的定時器,每一個都會有一個堵塞的 goroutine,雖然不會對你程式造成 panic,但我還是感覺不是很好。

我的版本

先上代碼:

package ticktock

import (
	"time"
)
// 這個結構體內容是為了相容 Ticker 的使用方式
type tickerStart struct {
	C      chan time.Time
	ticker *time.Ticker
	close  chan struct{}
}

func NewTickerStart(d time.Duration) *tickerStart {
    // 這裡我們創建的 channel 設了一個 buffer,原因是我們需要
    // 在下麵 Start 方法中及時推送當前時間而不至於堵塞。
    // 
	c := make(chan time.Time, 1) 
	return &tickerStart{ticker: time.NewTicker(d), C: c, close: make(chan struct{})}
}
// 這是我們核心的方法
func (ts *tickerStart) Start() {
	ts.C <- time.Now() // 首次觸發關鍵
	go func() {
		for {
			select {
			case _, ok := <-ts.close: // 用於關閉這個 goroutine
				if !ok {
					return
				}
			case t := <-ts.ticker.C: 
			// 把go原生定時器 push 的時間推送到我們定義的 time channel 中
				ts.C <- t
			}

		}
	}()
}
// 相容 ticker
func (ts *tickerStart) Reset(d time.Duration) {
	ts.ticker.Reset(d)
}
// 相容 ticker
func (ts *tickerStart) Stop() {
	ts.ticker.Stop()
	close(ts.close)
}

使用代碼如下:

package main

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

func main() {
	fmt.Println("start_time#", time.Now().Unix())
	chanClose := make(chan struct{})
	tts := ticktock.NewTickerStart(5 * time.Second)
	tts.Start()
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case <-chanClose:
				return
			case <-tts.C:
				fmt.Println("run_time#", time.Now().Unix())
			}
		}
	}()

	go func() {
		time.Sleep(10 * time.Second)
		chanClose <- struct{}{}
		tts.Stop()
	}()
	wg.Wait()
}

執行返回內容如下:

start_time# 1656861872
run_time# 1656861872
run_time# 1656861877

可以看到,和我們想要的一致。但和官方給出的不同我們不會堵塞 goroutine 。

最後

這是我在寫虛擬貨幣賬戶監控項目中碰到的其中一個問題,我也會在後續的文章中寫一寫我碰到的其他問題。當然這個項目會開源,可以關註我的 github GanymedeNil's github

最後的最後

說說我自己,從2月底從好未來辭職(在一家公司呆了5年越來越感覺自己呆在舒適區不想出來,但還是希望能有更多的提升所以自己提出了辭職,我在部門基本屬於是什麼技術都願意瞭解也會深入調研得到成本和收益的關係,人際關係在部門也是作為核心紐帶的角色。),到斷斷續續找工作,因為大專學歷問題,期間只接了一些小公司的 offer 但是最後都拒絕了,自己也好好評估了一下這幾個公司給到的職位和工作內容並不太符合我在接下來5年對自己職業規劃。所以開始自己寫開源項目,然後再找找志同道合的朋友,看有沒有合適的機會一起共事。如果你對我感興趣或者想和我聊聊可以加我微信 ganymede-nil和下載我的簡歷,註明加我的理由,防止被我當作營銷人員

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LVM: LVM: Logical Volume Manager,可以實現動態的擴容和縮容。邏輯捲是一種邏輯上的管理方式,把一塊或多塊硬碟或分區邏輯的組合在一起,命令成一個捲組(VG),捲組的空間來自所有硬碟空間的總和。(組成邏輯捲的硬碟或分區大小可以不一樣) VG: 多個磁碟或者分區組合在一起的( ...
  • 前言 前面我已經搭建好了ElasticSearch服務,並完成了MySQL到ElasticSearch的數據遷移; 使用ElasticSearch的初衷就是為了大數據搜索,本文將介紹ElaticSearch中各種查詢方法; 一、精確查詢(termQuery) termQuery不會對查詢條件進行分詞 ...
  • Android高仿網易雲音樂-啟動界面實現和動態許可權處理,啟動界面基本上沒有什麼難點,就是佈局,然後顯示用戶協議對話框,動態處理許可權,判斷是否顯示引導界面,是否顯示廣告界面等。 ...
  • 記錄我第一次使用Android Studio時遇到的問題以及一些簡單的筆記。 我所使用的是Android Studio 2.2版本 遇到的問題 創建一個Hello World!項目無疑是相當簡單的,我很快就完成了項目的創建過程。 然後……就報錯了。 Error:A problem occurred ...
  • 記錄使用typescript配合webpack打包一個javascript library的配置過程. 目標是構建一個可以同時支持`CommonJs`, `esm`, `amd`這個幾個js模塊系統的javascript庫, 然後還有一個單獨打包出一個css的樣式文件的需求. ...
  • jQuery 什麼是jQuery? jQuery是一個實用的JavaScript函數庫,jQuery極大地簡化了JS對DOM的操作,封裝並實現的一系列常用的方法。 jQuery庫封裝了JavaScript常用的功能代碼,提供一種簡便的JavaScript設計模式,優化HTML文檔操作、事件處理、動畫 ...
  • 微服務可以對你的企業產生積極的影響。因此,值得瞭解如何處理微服務架構(MSA)和一些微服務的設計模式,以及,微服務架構的一般目標或原則。以下是微服務架構方法中需要考慮的四個目標。 降低成本。MSA將降低設計、實施和維護IT服務的總體成本。 提高發佈速度:MSA將提高從想法到部署服務的速度。 提高複原 ...
  • springboot項目上傳存儲圖片到七牛雲伺服器 問題描述: 當圖片存在本地時會出現卡頓的現象。比如一篇圖文混排的文章,如果圖片沒有載入完,可能整個文章都顯示不出來,因為它們都是用的同一個伺服器。 但是如果把圖片單獨拿出來放在雲伺服器上進行載入,這樣圖片的載入和文字的載入互不幹擾,就可以優化這個問 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...