[toc]
oslo_concurrency模塊可以對OpenStack中的代碼塊提供線程鎖,以及進程鎖。由于多線程在Python中使用場景不多,因此不討論oslo_concurrency的線程鎖,主要討論oslo_concurrency進程鎖的實現(xiàn)細(xì)節(jié)。
一、oslo_concurrency的進程鎖
oslo_concurrency的進程鎖基于fasteners模塊,而fasteners模塊中通過linux的內(nèi)核接口函數(shù)lockf(...)對文件進行加鎖。
# oslo_concurrency的進程鎖接口
def external_lock(name, lock_file_prefix=None, lock_path=None):
lock_file_path = _get_lock_path(name, lock_file_prefix, lock_path)
# InterProcessLock來自fasteners模塊,lock_file_path為需要加鎖的文件地址
return InterProcessLock(lock_file_path)
# fasteners模塊中的InterProcessLock類
# 為了實現(xiàn)操作系統(tǒng)的通用性,windows系統(tǒng)通過msvcrt對文件加鎖
if os.name == 'nt':
import msvcrt
InterProcessLock = _WindowsLock
else:
import fcntl
InterProcessLock = _FcntlLock
class _FcntlLock(_InterProcessLock):
"""Interprocess lock implementation that works on posix systems."""
# 通過lockf可以實現(xiàn)以下操作
# LOCK_SH:共享鎖
# LOCK_EX:排他鎖
# LOCK_NB:非阻塞鎖(可以和LOCK_SH、LOCK_EX一起使用)
# LOCK_UN:釋放鎖
def trylock(self):
fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
def unlock(self):
fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
從oslo_concurrency源碼分析,oslo_concurrency進程鎖最終調(diào)用fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)實現(xiàn),在他傳入的參數(shù)中LOCK_EX表示獨占鎖,LOCK_NB表示無論是否請求到鎖,函數(shù)立即返回。
二、Linux中的文件鎖
oslo_concurrency模塊通過fcntl模塊實現(xiàn)鎖功能。實際上是fcntl模塊一個接口代理模塊,fcntl.py中沒有定義任何代碼邏輯,而是調(diào)用了/usr/lib64/python2.7/lib-dynload/fcntlmodule.so目錄下的C語言接口,即Linux的文件鎖接口函數(shù)。
在Linux中有三個文件鎖接口函數(shù)flock(...)、fcntl(...)、lockf(...),這三個接口可以創(chuàng)建Linux中不同類型的鎖。下面簡單介紹一下Linux中不同類型的鎖
Linux中支持四種功能的文件鎖:勸告鎖、強制鎖、租賃鎖、共享模式鎖,其中后兩種鎖都是強制鎖的變種。
| 鎖的類型 | 實現(xiàn)接口 | 鎖的功能 |
|---|---|---|
| 勸告鎖 | flock(...)、 fcntl(...) 、 lockf(...) | 勸告鎖是一種協(xié)同工作的鎖。對于這一種鎖來說,內(nèi)核只提供加鎖以及檢測文件是否已經(jīng)加鎖的手段,但是內(nèi)核并不參與鎖的控制和協(xié)調(diào)。 |
| 強制鎖 | fcntl(...) lockf(...) | 強制鎖是一種內(nèi)核強制采用的文件鎖。每當(dāng)有系統(tǒng)調(diào)用 open()、read() 以及write()發(fā)生的時候,內(nèi)核都要檢查并確保這些系統(tǒng)調(diào)用不會違反在所訪問文件上加的強制鎖約束。 |
| 租賃鎖 | fcntl(...) | 租賃鎖實際上是強制鎖的變種,當(dāng)進程嘗試打開一個被租借鎖保護的文件時,該進程會被阻塞,同時,在一定時間內(nèi)擁有該文件租借鎖的進程會收到一個信號。收到信號之后,擁有該文件租借鎖的進程會首先更新文件,從而保證了文件內(nèi)容的一致性,接著,該進程釋放這個租借鎖。如果擁有租借鎖的進程在一定的時間間隔內(nèi)沒有完成工作,內(nèi)核就會自動刪除這個租借鎖或者將該鎖進行降級,從而允許被阻塞的進程繼續(xù)工作。 |
| 共享模式鎖 | flock(...) | 共享模式強制鎖可以用于某些私有網(wǎng)絡(luò)文件系統(tǒng),如果某個文件被加上了共享模式強制鎖,那么其他進程打開該文件的時候不能與該文件的共享模式強制鎖所設(shè)置的訪問模式相沖突。但是由于可移植性不好,因此并不建議使用這種鎖。 |
關(guān)于flock(...)、lockf(…)、fcntl(...)的說明:
- flock(...):適用于勸告鎖,只能實現(xiàn)對整個文件進行加鎖,而不能實現(xiàn)記錄級的加鎖。但是flock實現(xiàn)了共享強制鎖;
- fcntl(...):適用于勸告鎖和強制鎖,不僅能夠?qū)φ麄€文件進行加鎖,而且能夠?qū)ξ募械挠涗?若干字節(jié))加鎖,實際上屬于posix系列的鎖。
- lockf(...):由于fcntl(...)函數(shù)功能非常多,文件鎖只是其中一個。lockf(...)是fcntl(...)鎖功能的包裝。
三、通過Python調(diào)用Linux文件鎖接口函數(shù)
#!/usr/bin/python
# coding:utf8
import os
import fcntl
import struct
def my_fcntl(fd, cmd, l_type, l_len=0, l_whence=0, l_start=0):
"""
調(diào)用fcntl函數(shù)
:param fd: 文件句柄,可以這樣獲?。? fobj = open(name, 'w')
fd = fobj.fileno()
:param cmd: fcntl函數(shù)要執(zhí)行的操作,于文件鎖相關(guān)的有:
fcntl.F_SETLKW :請求文件鎖,如果鎖被占用,那么等待
fcntl.F_GETLK :檢查鎖的狀態(tài),或者對文件鎖unlock
fcntl.F_SETLK :請求文件鎖,如果鎖被占用,拋出異常
:param l_type: 鎖的類型
fcntl.F_WRLCK :寫文件鎖
fcntl.F_RDLCK :讀文件鎖
fcntl.F_UNLCK :釋放鎖
:param l_len: 需要鎖的字節(jié)長度
:param l_whence:度量l_start,0表示從頭開始,1表示從當(dāng)前位置開始,2表示從文件結(jié)尾開始
:param l_start:需要鎖定的字節(jié)的起始位置
:return:
"""
flock = struct.pack('hhllhh', l_type, l_whence, l_start, l_len, 0, os.getpid())
flock_msg = fcntl.fcntl(fd, cmd, flock)
return struct.unpack('hhllhh', flock_msg)
def my_flock(fd,lock_type):
"""
調(diào)用flock對文件加鎖
:param fd: 文件句柄
:param lock_type:
# LOCK_SH: 共享鎖
# LOCK_EX: 排他鎖
# LOCK_NB: 非阻塞鎖(可以和LOCK_SH、LOCK_EX一起使用)
# LOCK_UN: 釋放鎖
# LOCK_MAND: 創(chuàng)建共享強制鎖,當(dāng)時并非所有文件系統(tǒng)都實現(xiàn)了這個模式
# LOCK_READ: 配合LOCK_MAND使用
# LOCK_WRITE: 配合LOCK_MAND使用
:return:
"""
fcntl.flock(fd,lock_type)
四、NFS的加鎖機制
在NFS2、NFS3協(xié)議本身不支持文件鎖,實現(xiàn)對NFS文件系統(tǒng)的中文件加鎖需要通過Network Lock Manager(NLM)這個輔助工具。NFS4協(xié)議在協(xié)議中實現(xiàn)了文件鎖,使NFS可以脫離NLM管理文件鎖。
在一個NFS目錄下對某個文件加鎖
touch locker
flock -xn "locker" -c "sleep 1000"
登錄NFS4服務(wù)端,查看文件的鎖狀態(tài)(這里的服務(wù)端時NetApp,通過vserver locks show命令可以看到哪些文件被加了鎖)。

從服務(wù)端返回的信息來看,/NFS/locker文件上有兩個鎖,share-level模式的鎖是NFS協(xié)議本身的文件鎖,而byte-range模式的鎖是flock命令施加的!
參考man nfs,可以發(fā)現(xiàn)以下兩個和NFS文件鎖相關(guān)的mount參數(shù):
對應(yīng)命令
mount -o lock -o local_lock=none <ip>:/<space> <standpoint>
| 參數(shù) | 說明 |
|---|---|
| lock/ nolock | 選擇是否使用 NLM協(xié)議來支持NFS的文件鎖(默認(rèn)情況下是lock) |
| local_lock | NFS文件是否采用本地鎖,可以有的選擇有all, flock, posix, none,對應(yīng)flock鎖和posix形式的鎖(fctnl函數(shù)生成的鎖),默認(rèn)情況下是none。 |
lock / nolock和local_lock選項是相互影響的,主要在NFS3協(xié)議中起作用,根據(jù)兩個參數(shù)的不同選項,有如下情形:
| mount參數(shù) | flock鎖 | posix鎖 |
|---|---|---|
| lock, local_lock=none | 所有NFS客戶端可見 | 所有NFS客戶端可見 |
| lock,local_lock=flock | 只有本地可見 | 所有NFS客戶端可見 |
| lock,local_lock=posix | 所有NFS客戶端可見 | 只有本地可見 |
| lock,local_lock=all | 只有本地可見 | 只有本地可見 |
| nolock,local_lock=all/flock/ posix | 只有本地可見 | 只有本地可見 |
| nolock,local_lock=none | 所有NFS客戶端可見 | 所有NFS客戶端可見 |
在NFS4協(xié)議下,由于協(xié)議本身自帶文件鎖狀態(tài),因此在NFS4協(xié)議下local_lock配置項是沒有用的,此時使用flock或者posix加鎖NFS所有客戶端都可見。
此外在NFS4協(xié)議下,打開文件還會生成一個share-level級別的共享鎖,如下圖:

其中,Sharelock Mode為read_write-deny_none,該模式中“read_write”表示某個客戶端以讀寫模塊式打開文件,“deny_none”表示打開文件的客戶端,不對其他客戶端的文件操作進行限制。
參考NFS4協(xié)議文檔,NFS客戶端可以編程控制NFS4共享鎖的模式:
# 獲取鎖的Client的操作
const OPEN4_SHARE_ACCESS_READ = 0x00000001;
const OPEN4_SHARE_ACCESS_WRITE = 0x00000002;
const OPEN4_SHARE_ACCESS_BOTH = 0x00000003;
# 未獲取鎖的Client被允許的操作
const OPEN4_SHARE_DENY_NONE = 0x00000000;
const OPEN4_SHARE_DENY_READ = 0x00000001;
const OPEN4_SHARE_DENY_WRITE = 0x00000002;
const OPEN4_SHARE_DENY_BOTH = 0x00000003;
五、參考
- linux的鎖機制
- NFS協(xié)議文檔:
linux:man nfs