程序單例執行,檔案加鎖
前言
很多時候我們都需要程序單例執行,當再次執行程式時檢查到已有程式在執行可以做特別的操作,比如置頂已執行的程式,比如當前程式提示一下就退出。
最簡單方案是開啟程序建立一個檔案,程式結束時刪除檔案,當第二個程式執行時判斷該檔案存在則認為已有程式執行。問題是程式異常退出沒有刪除那個檔案就GG了。
還有方案就是程序啟動時鎖住一個檔案,程序退出釋放鎖,程序異常退出由系統自動釋放鎖。這個就是完美的方案。
還有一種方案就是判斷程序名是否允許,Linux下可以執行【ps auxf | grep xxx】,Windows下可以執行【tasklist | findstr xxx】。問題就是程式檔名被改就GG。
另外還可以監聽一個tcp埠,如果第二個程序執行也監聽相同埠會報錯,因此也能實現程序單例執行,只是需要佔用一個埠。
flock命令
命令介紹
該工具是用來獲取一個檔案鎖,並執行命令。當檔案已經被鎖則不會執行命令,使用該命令可有效防止重複執行一些操作。特別是cron指令碼,由於指令碼執行週期長,下一次定時執行又到了,可以不重複執行。
Windows下也是可以安裝flock命令的,我是安裝【msys2】後預設就有了。下面看下該工具的幫助文件:
flock -h Usage: flock [options] <file>|<directory> <command> [<argument>...] flock [options] <file>|<directory> -c <command> flock [options] <file descriptor number> Manage file locks from shell scripts. Options: -s, --shared get a shared lock // 獲取讀鎖(共享鎖),多個程序可獲取並讀檔案,其他程序獲取寫鎖會返回失敗 -x, --exclusive get an exclusive lock (default) // 獲取寫鎖(排它鎖),只允許一個程序獲取,其他程序獲取均返回失敗 -u, --unlock remove a lock -n, --nonblock fail rather than wait // 當獲取鎖失敗時直接返回,不帶此選項程式會卡住,直到獲取到鎖 -w, --timeout <secs> wait for a limited amount of time // 獲取鎖超時的時間 -E, --conflict-exit-code <number> exit code after conflict or timeout // 當獲取鎖失敗時,返回的錯誤碼.在Linux執行【echo $?】,win執行【echo %errorlevel%】檢視 -o, --close close file descriptor before running command -c, --command <command> run a single command string through the shell // 鎖住檔案的同時執行一個命令,我一般用來執行一個指令碼 --verbose increase verbosity -h, --help display this help and exit -V, --version output version information and exit For more details see flock(1).
flock例項
程式碼實現
獲取檔案鎖
可以執行
go get github.com/jan-bar/golibs
,下載我寫好的庫,關於Windows和Linux下獲取檔案鎖和釋放檔案鎖。
下面的程式碼,同時執行2個程序會返回錯誤file is lock
,請看示例程式:
package main import ( "os" "strconv" "time" "github.com/jan-bar/golibs/filelock" ) func main() { fr, err := filelock.LockOpenFile("a.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666, filelock.WriteLock) if err != nil { panic(err) } defer fr.Close() fr.File.Write([]byte(strconv.Itoa(os.Getpid()))) time.Sleep(time.Second * 5) }
檔案鎖判斷單例
可以根據返回錯誤值判斷檔案是否被鎖住,如果鎖住就認為已有程序在執行。
package main
import (
"fmt"
"github.com/jan-bar/golibs"
)
func main() {
err := golibs.SingletonFile("a.txt")
if err == golibs.ErrSingleton {
fmt.Println("已有程序在執行")
}
}
tcp埠判斷單例
如果本機指定埠已經被監聽,則證明已有程式在執行。
package main
import (
"fmt"
"github.com/jan-bar/golibs"
)
func main() {
err := golibs.SingletonTcp(8080)
if err == golibs.ErrSingleton {
fmt.Println("已有程序在執行")
}
}
總結
程序單例執行在很多場景都是有必要的,上面介紹的幾種方案,我比較喜歡通過加鎖檔案來判斷程序是否已經被執行過,因為只佔用一個檔案。很多工具都是通過鎖檔案,並將自己的pid
寫入檔案,方便其他操作去讀取這個程序的pid,而不用通過ps去查詢,堪稱完美。我只測試了Windows和Linux下的檔案鎖功能,Windows是使用win32api去實現,Linux是系統自帶的介面。