http://blog.csdn.net/jianhong1990/article/details/26370519
http://yunjianfei.iteye.com/blog/2061756
http://zhou123.blog.51cto.com/4355617/1650185
https://blog.jamespan.me/posts/deadlock-with-python-fcntl-flock
python的文件锁目前使用的是fcntl这个库,它实际上为 Unix上的ioctl
,flock和fcntl
函数提供了一个接口。
1.fcntl库的简单使用
- import fcntl
- import os, time
- FILE = "counter.txt"
- if not os.path.exists(FILE):
- # create the counter file if it doesn't exist
- file = open(FILE, "w")
- file.write("0")
- file.close()
- for i in range(20):
- file = open(FILE, "r+") #由于flock生成的是劝告锁,不能阻止进程对文件的操作,所以这里可以正常打开文件
- fcntl.flock(file.fileno(), fcntl.LOCK_EX) #为了避免同时操作文件,需要程序自己来检查该文件是否已经被加锁。这里如果检查到加锁了,进程会被阻塞
- print 'acquire lock'
- counter = int(file.readline()) + 1
- file.seek(0)
- file.write(str(counter))
- print os.getpid(), "=>", counter
- time.sleep(10)
- file.close() # unlocks the file
- print 'release lock'
- time.sleep(3)
分别启动2个进程来同时运行这个脚本,我们可以很明显的看到2者互相之间交替阻塞。同一时刻只有一个进程能够对counter.txt文件进行操作。
2.对fcntl.flock()函数的说明:
linux的flock() 的函数原型如下所示:
int flock(int fd, int operation);其中,参数 fd 表示文件描述符;参数 operation 指定要进行的锁操作,该参数的取值有如下几种:
LOCK_SH:表示要创建一个共享锁,在任意时间内,一个文件的共享锁可以被多个进程拥有;
LOCK_EX:表示创建一个排他锁,在任意时间内,一个文件的排他锁只能被一个进程拥有;
LOCK_UN:表示删除该进程创建的锁;
LOCK_MAND:它主要是用于共享模式强制锁,它可以与 LOCK_READ 或者 LOCK_WRITE联合起来使用,从而表示是否允许并发的读操作或者并发的写操作;
通常情况下,如果加锁请求不能被立即满足,那么系统调用 flock()会阻塞当前进程。比如,进程想要请求一个排他锁,但此时,已经由其他进程获取了这个锁,那么该进程将会被阻塞。如果想要在没有获得这个排他锁的情况下不阻塞该进程,可以将LOCK_NB 和 LOCK_SH 或者 LOCK_EX 联合使用,那么系统就不会阻塞该进程。flock()所加的锁会对整个文件起作用。
注意:
1. 对于文件的 close() 操作会使文件锁失效;
2. 同理,进程结束后文件锁失效;
3. flock() 的 LOCK_EX是“劝告锁”,系统内核不会强制检查锁的状态,需要在代码中进行文件操作的地方显式检查才能生效。
3.相关资料
1.Linux中的文件锁的概念及其实现(http://blog.csdn.net/jianhong1990/article/details/26369465)
2.fcntl模块的官方文档(https://docs.python.org/2/library/fcntl.html#fcntl.flock)
===============================================
值得注意的是,在给文件加锁之前,一定要保证文件以相应的访问模式打开,例如要对一个文件加上共享锁,一定要首先按读模式打开文件,若要给文件加上排他锁,则首先要按写模式打开对应文件;若想加两种锁,则需要按读写模式打开.
跨进程锁的实现方式中,基于文件锁的方式相对来说好一点。以下贴出一个简单的代码:
- import os
- import fcntl
- class Lock:
- def __init__(self, filename):
- self.filename = filename
- # This will create it if it does not exist already
- self.handle = open(filename, 'w')
- # Bitwise OR fcntl.LOCK_NB if you need a non-blocking lock
- def acquire(self):
- fcntl.flock(self.handle, fcntl.LOCK_EX)
- def release(self):
- fcntl.flock(self.handle, fcntl.LOCK_UN) //最好改成fcntl.lockf
- def __del__(self):
- self.handle.close()
- # Usage
- try:
- lock = Lock("/tmp/lock_name.tmp")
- lock.acquire()
- # Do important stuff that needs to be synchronized
- finally:
- lock.release()
可以同时运行多份该程序来进行试验。
这种方式的锁有以下特点:
1. 锁文件只在第一次调用的时候创建。
2. 锁是通过kernel来控制的,不消耗磁盘IO
3. 这种方式的锁只对同一个OS中的进程有效。跨服务器、OS是无效的,这时候需要选用分布式锁,比如Elock http://dustin.sallings.org/elock/
==============================
一不小心被文件锁坑了然后就死锁了
文件锁我用的是 fcntl.flock
,通过 strace 命令查看进程正在进行的系统调用,确实能看到进程阻塞在 flock
函数上,cat /proc/locks
能看到目标进程除了正在持有一个 FLOCK,还有另外两个尝试获取锁的操作,只不过很不幸地被正在持有的锁阻塞了。
文件锁这种东西就是比较坑,没有什么方法能够从外部让进程主动释放,除非杀死进程,或者换一个文件。但是出乎我意料的是,即便我把目标进程重启了,它还是会阻塞在获取锁的操作上,从 /proc/locks
来看,锁确实没有被释放,而持有锁的进程,则是一个早已不存在的进程。
灵异事件!不存在的进程居然还能持有文件锁!
一番探寻之后,用 lsof
发现了真相:持有文件锁的,不是所谓的不存在的进程,而是这个不存在的进程的子进程。在我贫瘠的知识中,只知道大多数时候 fork 出来的子进程会持有父进程持有的文件描述符(aka fd),但是文件锁这种东西也会被继承?
上网查询了相关资料,我才发现有一种坑爹的文件锁,是真的有可能伴随着 fd 一起被子进程继承的,非常不幸,这种文件锁恰好就是我用的那个,fcntl.flock
。
既然知道了原因,解决起来就容易了,要么把文件锁换成 fcntl.lockf
,要么在用 subprocess.Popen
创建子进程的时候,带上参数 close_fds=True
,让创建出来的子进程把除了 stdin,stdout,stderr 之外的 fd 全关掉,别从父进程带一大堆有的没的文件过来。
https://stackoverflow.com/questions/22409780/flock-vs-lockf-on-linux
In Linux, lockf()
is just a wrapper around fcntl()
, while flock()
locks are separate (and will only work on local filesystems, not on e.g. NFS mounts). That is, one process can have an advisory exclusive flock()
lock on a file, while another process has an advisory exclusive fcntl()
lock on that same file. Both are advisory locks, but they do not interact.
POSIX does not explicitly specify how lockf()
/flock()
/fcntl()
locks should interact, and there have been differences in the past. Now, the situation has calmed down a bit, and one can approximately say that
-
fcntl()
locks are the most reliableAcross architectures, they have the best chance of working right on e.g. shared filesystems -- NFS and CIFS mounts, for example.
-
Most often,
lockf()
is implemented as "shorthand" forfcntl()
The other alternative, as "shorthand" for
flock()
, is possible, but nowadays rare. -
fcntl()
andflock()
have different semantics wrt. inheritance and automatic releasesfcntl()
locks are preserved across anexec()
, but not inherited across afork()[fcntl/lockf的文件锁不会被子进程继承]
. The locks are released when the owning process closes any descriptor referring to the same file.