scheduler + lock

其實我很喜歡用一套 Ruby 的 gem,叫 rufus-scheduler

其實就是幾個重點,定時執行,但不能重複執行,定時執行類似 cronjob,但 conjob 不能保證同一時間只存在一個 process,再來用 cronjob 的話 process 全部都會從頭 initialize 過,包括 connection 和初始化檢查之類的,所以勢必需要 process 內的 scheduler,所以以上條件該 gem 都支援,甚至要不要重疊都能自選,不過該 gem 這邊有其他文章討論,所以不再說明更多

之後就開始找 golang 有沒有類似的東西,看來有,不過其無法保證唯一這件事情,因為內部實作應該都還是用 goroutine 才是,不過上個 lock 應該就能解決

package main

import (
	"fmt"
	"log"
	"math/rand"
	"sync"
	"time"

	"github.com/robfig/cron"
)

// LockMe 鎖我鎖我 yooooooo
type LockMe struct {
	lockMutex sync.Mutex
	isLocked  bool //mutex 無法取得是否鎖定的狀態,因此多個變數來紀錄
}

var lockMe LockMe

func main() {
	serialBuilder := 0
	cronJob := cron.New()
	spec := "*/2 * * * * *"

	lockMe = LockMe{
		lockMutex: sync.Mutex{},
		isLocked:  false,
	}

	cronJob.AddFunc(spec, func() {
		fmt.Println("start AddFunc")

		if lockMe.isLocked { // skip if locked
			log.Println("locked , skip")
			return
		}

		lockMe.lockMutex.Lock() //最慘最慘最慘這行會出事,取不到 lock 噴死
		fmt.Println("get lock , start process")

		time.Sleep(5 * time.Second) // DEMO 危險的一行,單純測試 deadlock

		lockMe.isLocked = true

		serialBuilder++
		serialSelf := serialBuilder // clone int,不 clone 會出事

		var randomSeed = rand.New(rand.NewSource(time.Now().UnixNano())) // timestamp as random seed
		var randomMe = randomSeed.Intn(10)                               // rand 0 ~ 10
		log.Println("cron start , serial : ", serialSelf, " , sleep : ", randomMe)
		time.Sleep(time.Duration(randomMe) * 1e9) // 1e9 = 1000000000
		log.Println("cron end , serial : ", serialSelf, " , awake : ", randomMe)

		lockMe.isLocked = false
		lockMe.lockMutex.Unlock()
	})
	cronJob.Start() // start cronJob goroutine
	select {}       // for{} => 100% CPU running , select{} => sleeping forever
}

裡面其實有實作競爭狀態 + 防止就是,有需要的人可以研究一下 : )