File lock + PID

server 寫作時,有些特殊任務的 server 不允許有 race condition 的狀況發生,通常有幾種方式來製作,類似使用 transaction lock 或是乾脆只有一台,且內部沒有類似 thread 的東西來搶佔資源( single process / single thread ),不過這邊說的並不是真正的 “single”,而是主要執行任務只有一條即可,這樣就可以無視 race condition 的存在(因為概念上而言也沒人來搶)

但這樣的 server 最怕的應該是 … 開了兩台哈哈,在這之前應該很多人都會用類似 eth 的 port 來做綁定,來保證這件事情,不過總是有改了設定檔又開啟的狀況對唄?,所以在這之前我習慣都會用 file lock 來做保證,然而你都用了 file 了,多一個空檔案又不知道能做啥,那就順便寫入 PID 唄

當然 file lock 和 file exist 是有差別的,前者會保證該 process 的存在,如果 kill 掉了 lock 自動會被釋放掉,而單純的 file exist … 缺點太多就是,anyway 以下為 demo code

package main

import (
	"fmt"
	"os"
	"syscall"
	"time"
)

var lockFilePath = "./process.pid"
var lockFileLock *os.File

func init() {
	// 開啟或新增,開啟使用 write only 模式
	lockFileLock, err := os.OpenFile(lockFilePath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		panic(fmt.Errorf("cannot flock directory %s", err))
	}
	// 加強制鎖
	err = syscall.Flock(int(lockFileLock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		panic(fmt.Errorf("cannot flock directory %s - %s", lockFilePath, err))
	}
	// 寫入 PID
	lockFileLock.WriteString(fmt.Sprintf("%d\n", os.Getpid()))
	// 可不用 sync / flush 因為不是 buffer 系列
	//lockFileLock.Sync()
}
func main() {
	fmt.Println("file locked and sleep 30s")
	time.Sleep(30 * 1e9)
	fmt.Println("finish & unlock")
}

開啟後會自動睡 30 秒,開第二個相同的會直接 panic 掉,這大概就是我想要的效果就是了 : )

1個讚