[toc]
oslo_concurrency模块可以对OpenStack中的代码块提供线程锁,以及进程锁。由于多线程在Python中使用场景不多,因此不讨论oslo_concurrency的线程锁,主要讨论oslo_concurrency进程锁的实现细节。
一、oslo_concurrency的进程锁
oslo_concurrency的进程锁基于fasteners模块,而fasteners模块中通过linux的内核接口函数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类
# 为了实现操作系统的通用性,windows系统通过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可以实现以下操作
# 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进程锁最终调用fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)实现,在他传入的参数中LOCK_EX表示独占锁,LOCK_NB表示无论是否请求到锁,函数立即返回。
二、Linux中的文件锁
oslo_concurrency模块通过fcntl模块实现锁功能。实际上是fcntl模块一个接口代理模块,fcntl.py中没有定义任何代码逻辑,而是调用了/usr/lib64/python2.7/lib-dynload/fcntlmodule.so目录下的C语言接口,即Linux的文件锁接口函数。
在Linux中有三个文件锁接口函数flock(...)、fcntl(...)、lockf(...),这三个接口可以创建Linux中不同类型的锁。下面简单介绍一下Linux中不同类型的锁
Linux中支持四种功能的文件锁:劝告锁、强制锁、租赁锁、共享模式锁,其中后两种锁都是强制锁的变种。
锁的类型 | 实现接口 | 锁的功能 |
---|---|---|
劝告锁 | flock(...)、 fcntl(...) 、 lockf(...) | 劝告锁是一种协同工作的锁。对于这一种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。 |
强制锁 | fcntl(...) lockf(...) | 强制锁是一种内核强制采用的文件锁。每当有系统调用 open()、read() 以及write()发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。 |
租赁锁 | fcntl(...) | 租赁锁实际上是强制锁的变种,当进程尝试打开一个被租借锁保护的文件时,该进程会被阻塞,同时,在一定时间内拥有该文件租借锁的进程会收到一个信号。收到信号之后,拥有该文件租借锁的进程会首先更新文件,从而保证了文件内容的一致性,接着,该进程释放这个租借锁。如果拥有租借锁的进程在一定的时间间隔内没有完成工作,内核就会自动删除这个租借锁或者将该锁进行降级,从而允许被阻塞的进程继续工作。 |
共享模式锁 | flock(...) | 共享模式强制锁可以用于某些私有网络文件系统,如果某个文件被加上了共享模式强制锁,那么其他进程打开该文件的时候不能与该文件的共享模式强制锁所设置的访问模式相冲突。但是由于可移植性不好,因此并不建议使用这种锁。 |
关于flock(...)、lockf(…)、fcntl(...)的说明:
- flock(...):适用于劝告锁,只能实现对整个文件进行加锁,而不能实现记录级的加锁。但是flock实现了共享强制锁;
- fcntl(...):适用于劝告锁和强制锁,不仅能够对整个文件进行加锁,而且能够对文件中的记录(若干字节)加锁,实际上属于posix系列的锁。
- lockf(...):由于fcntl(...)函数功能非常多,文件锁只是其中一个。lockf(...)是fcntl(...)锁功能的包装。
三、通过Python调用Linux文件锁接口函数
#!/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):
"""
调用fcntl函数
:param fd: 文件句柄,可以这样获取:
fobj = open(name, 'w')
fd = fobj.fileno()
:param cmd: fcntl函数要执行的操作,于文件锁相关的有:
fcntl.F_SETLKW :请求文件锁,如果锁被占用,那么等待
fcntl.F_GETLK :检查锁的状态,或者对文件锁unlock
fcntl.F_SETLK :请求文件锁,如果锁被占用,抛出异常
:param l_type: 锁的类型
fcntl.F_WRLCK :写文件锁
fcntl.F_RDLCK :读文件锁
fcntl.F_UNLCK :释放锁
:param l_len: 需要锁的字节长度
:param l_whence:度量l_start,0表示从头开始,1表示从当前位置开始,2表示从文件结尾开始
:param l_start:需要锁定的字节的起始位置
: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):
"""
调用flock对文件加锁
:param fd: 文件句柄
:param lock_type:
# LOCK_SH: 共享锁
# LOCK_EX: 排他锁
# LOCK_NB: 非阻塞锁(可以和LOCK_SH、LOCK_EX一起使用)
# LOCK_UN: 释放锁
# LOCK_MAND: 创建共享强制锁,当时并非所有文件系统都实现了这个模式
# LOCK_READ: 配合LOCK_MAND使用
# LOCK_WRITE: 配合LOCK_MAND使用
:return:
"""
fcntl.flock(fd,lock_type)
四、NFS的加锁机制
在NFS2、NFS3协议本身不支持文件锁,实现对NFS文件系统的中文件加锁需要通过Network Lock Manager(NLM)这个辅助工具。NFS4协议在协议中实现了文件锁,使NFS可以脱离NLM管理文件锁。
在一个NFS目录下对某个文件加锁
touch locker
flock -xn "locker" -c "sleep 1000"
登录NFS4服务端,查看文件的锁状态(这里的服务端时NetApp,通过vserver locks show命令可以看到哪些文件被加了锁)。
从服务端返回的信息来看,/NFS/locker文件上有两个锁,share-level模式的锁是NFS协议本身的文件锁,而byte-range模式的锁是flock命令施加的!
参考man nfs,可以发现以下两个和NFS文件锁相关的mount参数:
对应命令
mount -o lock -o local_lock=none <ip>:/<space> <standpoint>
参数 | 说明 |
---|---|
lock/ nolock | 选择是否使用 NLM协议来支持NFS的文件锁(默认情况下是lock) |
local_lock | NFS文件是否采用本地锁,可以有的选择有all, flock, posix, none,对应flock锁和posix形式的锁(fctnl函数生成的锁),默认情况下是none。 |
lock / nolock和local_lock选项是相互影响的,主要在NFS3协议中起作用,根据两个参数的不同选项,有如下情形:
mount参数 | 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协议下,由于协议本身自带文件锁状态,因此在NFS4协议下local_lock配置项是没有用的,此时使用flock或者posix加锁NFS所有客户端都可见。
此外在NFS4协议下,打开文件还会生成一个share-level级别的共享锁,如下图:
其中,Sharelock Mode为read_write-deny_none,该模式中“read_write”表示某个客户端以读写模块式打开文件,“deny_none”表示打开文件的客户端,不对其他客户端的文件操作进行限制。
参考NFS4协议文档,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协议文档:
linux:man nfs