這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
使用 golang 的定時任務(採用 robfig/cron 包),每 5s 調用一個函數進行處理,但是這個函數的處理可能會耗時,在沒有執行完之前如果再次調用,就會導致計算結果不對。
如定時任務:
// 5s 定時任務func run5Second() {spec := "*/5, *, *, *, *, *"c := cron.New()//npc重新整理c.AddFunc(spec, modelFish.NpcRefresh)c.Start()}
NpcRefresh 方法定義:
// NpcRefresh 返回需要重新整理的NPCfunc NpcRefresh() {online := getOnlineRoomIds()//online := []string{"5"}fmt.Println("online room:", online)if len(online) > 0 {for _, roomID := range online {roomID, _ := strconv.Atoi(roomID)go doNpcRefresh(roomID)}}}
這裡 doNpcRefresh 方法起了多個 goroutine 去並發執行,裡面有大量的計算,發現如果重複調用就會導致計算結果不正確,那麼在每次的執行結束之前,不應該再次執行。
一開始想用 sync.waitGroup 的方式去同步,它的原理是把要執行的 goroutine 放隊列,執行完出隊列,隊列沒有完成前阻塞。這是一個方法,這樣確實可以達到不重複執行一個未完成函數的目的,但是不適用當前的情境。
sync.waitGroup 用法樣本:
package main import ( "fmt" "sync") func main() { var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) } for i := 0; i < 100; i++ { go wg.Done() } fmt.Println("exit") wg.Wait()} func add(wg sync.WaitGroup) { wg.Add(1)} func done(wg sync.WaitGroup) { wg.Done()}
這個情境要解決的問題是:上一輪定時任務調用的函數沒有執行完,就不執行,否則再次執行,而 sync.waitGroup 方式只解決了不重複執行,但是無法“丟棄”這次調用,而是放到隊列等後面執行,這樣也許會累積很多隊列,這不是期望的。
因此,解決辦法就是,在函數開始執行的時候,在 Redis 設定一個值表示未完成,執行結束的地方更新這個值為已完成,在每次執行最前面判斷這個值的狀態,如果是未完成,就直接返回 false.
大概流程:
// 執行具體遊戲房間的npc重新整理func doNpcRefresh(roomid int) bool {status := getRefreshStatus(roomid)if status == "0" {fmt.Printf("房間%d重新整理未完成,不進行新一輪重新整理\n", roomid)return false}start := time.Now()fmt.Println("====================檢測NPC重新整理====================")setRefreshStart(roomid) //此處省略800子setRefreshDone(roomid)return true}
如此一來,就解決了問題。
注意:如果進程被意外 kill 掉,會導致 Redis 中這個值的狀態永遠都是未完成,那麼重啟服務後也就無法重新調用同一個函數了。這是個需要解決的問題。不過本情境暫時不需要考慮這種情況。
594 次點擊