flock 之前,先來聊聊為什麼會需要 flock 。

如果有多個 Processes 同時對同 1 個檔案進行讀寫操作,很容易就會發生同步問題,這時候最簡單的解決的方法就是用個鎖( Lock )確保同一時間只有 1 個 Process 可以對 1 個共用的檔案進行操作。

而 flock 就是幫忙建立 Lock 的一個機制,其原理是利用 1 個檔案作為鎖(Lock) , Process 要對某個檔案進行讀寫操作時,得先獲得鎖(Lock) 才能進行讀寫操作,其他沒獲得鎖(Lock)的 Process 則會被阻擋無法執行。

另一種常見的情況是為了確保 Crontab 內定期執行的程式同一時間只會有 1 個 Process 在執行,也會利用 flock 避免因為下一次定期執行時間到而又執行一個新的 Process 。

flock 除了可以用 flock 指令之外, Python 也內建提供 fcntl 模組幫我們建立/獲取 flock 鎖。

範例

以下是利用 fcntl.flock(fd, op) 建立 flock 的範例,可以看到主要是 fcntl.flock(f, fcntl.LOCK_EX) 中的參數 fcntl.LOCK_EX | fcntl.LOCK_NB 建立一個同一時間只有 1 個 Process 能夠操作的鎖,其中 fcntl.LOCK_EX 代表建立 exclusive lock 確保同一時間只有 1 個 Process 可以獲取鎖(Lock),而 fcntl.LOCK_NB 則是代表其他沒獲取到鎖(Lock)則不要一直等待獲取到鎖,而是直接 raise OSError

import fcntl
from time import sleep


lockfile = '/tmp/fcntl.lock'

try:
    f = open(lockfile, 'r')
except IOError:
    f = open(lockfile, 'wb')

try:
    fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    sleep(600)
except OSError:
    print('locked')
finally:
    fcntl.flock(f, fcntl.LOCK_UN)

上述範例執行之後會 sleep 10 分鐘,如果這時候有其他的程式想要同樣想要透過 fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) 獲取鎖就會失敗而列印出 locked (可以複製上述程式到新的檔案並執行,即可看到效果)。

最後經過 10 分鐘之後或者有任何 IOError 以外的 Exception 的話則會進到 finally 區塊中, fcntl.flock(f, fcntl.LOCK_UN) 就會將鎖釋出,避免下次執行時遇到一直遲遲沒釋放的鎖導致程式無法執行。

將 flock 包裝成 decorator

因為 flock 的使用也不算罕見,所以可以將 flock 包裝成 decorator ,例如以下的 exclusive_lock decorator 就是確保某個 function 同一時間只能有一個 Process 在執行:

import os
import fcntl
from time import sleep


def exclusive_lock(fn):
    lockfile = os.path.join('/tmp/', 'ex_{}.lock'.format(fn.__name__))
    def wrapped_func(*args, **kwargs):
        try:
            fd = open(lockfile, 'r')
        except IOError:
            fd = open(lockfile, 'wb')

        try:
            fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            fn(*args, **kwargs)
        except IOError:
            pass
        finally:
            fcntl.flock(fd, fcntl.LOCK_UN)
    return wrapped_func


@exclusive_lock
def only_once_process_can_sleep():
    sleep(100)


if __name__ == '__main__':
    only_once_process_can_sleep()

上述程式可以開多個 Terminal 執行看看,不管多少個 Terminal 執行該程式,同時間就只會有 1 個 Process 進入 sleep 。

flock 該注意的事

Linux 上的 Lock 有 advisory lock & mandatory lock 的區別,advisory lock 指的是要 Process 主動去獲取鎖才能夠作用,而 mandatory lock 是作業系統提供的鎖,不需要 Process 自己去獲取就會起作用。而 flock 屬於 advisory lock ,意思是假設 A 與 B 2 個 Processes ,假設 A 獲取了鎖(Lock)情況下 ,如果要確保同一時間只有一個 Process 可獲取鎖,那麼 B 在執行時也必須主動去獲取鎖,如果 B 忘記去獲取鎖,將導致 A B 都在同一時間可以執行,使得該鎖沒有起任何作用。

References

https://docs.python.org/3/library/fcntl.html

https://linux.die.net/man/2/flock