這里的進(jìn)程鎖與線程鎖、互斥量、讀寫鎖和自旋鎖不同,它是通過記錄一個(gè)PID文件,避免兩個(gè)進(jìn)程同時(shí)運(yùn)行的文件鎖。
進(jìn)程鎖的作用之一就是可以協(xié)調(diào)進(jìn)程的運(yùn)行,例如crontab使用進(jìn)程鎖解決沖突提到,使用crontab限定每一分鐘執(zhí)行一個(gè)任務(wù),但這個(gè)進(jìn)程運(yùn)行時(shí)間可能超過一分鐘,如果不用進(jìn)程鎖解決沖突的話兩個(gè)進(jìn)程一起執(zhí)行就會(huì)有問題。后面提到的項(xiàng)目實(shí)例Run也有類似的問題,通過進(jìn)程鎖可以解決進(jìn)程間同步的問題。
使用PID文件鎖還有一個(gè)好處,方便進(jìn)程向自己發(fā)停止或者重啟信號(hào)。Nginx編譯時(shí)可指定參數(shù)--pid-path=/var/run/nginx.pid,進(jìn)程起來后就會(huì)把當(dāng)前的PID寫入這個(gè)文件,當(dāng)然如果這個(gè)文件已經(jīng)存在了,也就是前一個(gè)進(jìn)程還沒有退出,那么Nginx就不會(huì)重新啟動(dòng)。進(jìn)程管理工具Supervisord也是通過記錄進(jìn)程的PID來停止或者拉起它監(jiān)控的進(jìn)程的。
進(jìn)程鎖在特定場景是非常適用的,而操作系統(tǒng)默認(rèn)不會(huì)為每個(gè)程序創(chuàng)建進(jìn)程鎖,那我們該如何使用呢?
其實(shí)要實(shí)現(xiàn)一個(gè)進(jìn)程鎖很簡單,通過文件就可以實(shí)現(xiàn)了。例如程序開始運(yùn)行時(shí)去檢查一個(gè)PID文件,如果文件存在就直接退出,如果文件不存在就創(chuàng)建一個(gè),并把當(dāng)前進(jìn)程的PID寫入文件中。這樣我們很容易可以實(shí)現(xiàn)讀鎖,但是所有流程都需要自己控制。
當(dāng)然根據(jù)DRY(Don't Repeat Yourself)原則,Linux已經(jīng)為我們提供了flock接口。
Flock提供的是advisory lock,也就是建議性的鎖,其他進(jìn)程實(shí)際上也可以讀寫這個(gè)鎖文件。Linux上可以直接使用flock命令,使用C可以調(diào)用原生的flock接口,這里詳細(xì)介紹Go 1.3引入的FcntlFock()。
我們封裝了簡單的接口。
// Control the lock of file.
func fcntlFlock(lockType int16, path ...string) error {
var err error
if lockType != syscall.F_UNLCK {
mode := syscall.O_CREAT | syscall.O_WRONLY
lockFile, err = os.OpenFile(path[0], mode, 0666)
if err != nil {
return err
}
}
lock := syscall.Flock_t{
Start: 0,
Len: 1,
Type: lockType,
Whence: int16(os.SEEK_SET),
}
return syscall.FcntlFlock(lockFile.Fd(), syscall.F_SETLK, &lock)
}
這樣對進(jìn)程加鎖。
// Lock the file.
func Flock(path string) error {
return fcntlFlock(syscall.F_WRLCK, path)
}
這樣對進(jìn)程解鎖。
// Unlock the file.
func Funlock(path string) error {
err := fcntlFlock(syscall.F_UNLCK)
if err != nil {
return err
} else {
return lockFile.Close()
}
}
學(xué)習(xí)完進(jìn)程鎖,我們開始了解各種進(jìn)程,如孤兒進(jìn)程、僵尸進(jìn)程。