Python - fcntl.flock(fd, op) 檔案鎖應用範例

Posted on  May 8, 2018  in  Python 程式設計 - 高階  by  Amo Chen  ‐ 2 min read

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

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!