博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

文件锁 python 进程间锁 fcntl

Posted on 2017-09-27 17:35  bw_0927  阅读(5839)  评论(0编辑  收藏  举报

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上的ioctlflock和fcntl 函数提供了一个接口。

1.fcntl库的简单使用

  1. import fcntl  
  2. import os, time  
  3.   
  4. FILE = "counter.txt"  
  5.   
  6. if not os.path.exists(FILE):  
  7.     # create the counter file if it doesn't exist  
  8.     file = open(FILE, "w")  
  9.     file.write("0")  
  10.     file.close()  
  11.   
  12. for i in range(20):  
  13.     file = open(FILE, "r+")     #由于flock生成的是劝告锁,不能阻止进程对文件的操作,所以这里可以正常打开文件  
  14.     fcntl.flock(file.fileno(), fcntl.LOCK_EX)   #为了避免同时操作文件,需要程序自己来检查该文件是否已经被加锁。这里如果检查到加锁了,进程会被阻塞      
  15.     print 'acquire lock'  
  16.     counter = int(file.readline()) + 1  
  17.     file.seek(0)  
  18.     file.write(str(counter))  
  19.     print os.getpid(), "=>", counter  
  20.     time.sleep(10)  
  21.     file.close() # unlocks the file  
  22.     print 'release lock'  
  23.     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)

 

 

===============================================

值得注意的是,在给文件加锁之前,一定要保证文件以相应的访问模式打开,例如要对一个文件加上共享锁,一定要首先按读模式打开文件,若要给文件加上排他锁,则首先要按写模式打开对应文件;若想加两种锁,则需要按读写模式打开.

跨进程锁的实现方式中,基于文件锁的方式相对来说好一点。以下贴出一个简单的代码:

  1. import os  
  2. import fcntl  
  3.   
  4. class Lock:   
  5.     def __init__(self, filename):  
  6.         self.filename = filename  
  7.         # This will create it if it does not exist already  
  8.         self.handle = open(filename, 'w')  
  9.       
  10.     # Bitwise OR fcntl.LOCK_NB if you need a non-blocking lock   
  11.     def acquire(self):  
  12.         fcntl.flock(self.handle, fcntl.LOCK_EX)  
  13.           
  14.     def release(self):  
  15.         fcntl.flock(self.handle, fcntl.LOCK_UN)            //最好改成fcntl.lockf
  16.           
  17.     def __del__(self):  
  18.         self.handle.close()  
  19.   
  20. # Usage  
  21. try:  
  22.     lock = Lock("/tmp/lock_name.tmp")  
  23.     lock.acquire()  
  24.     # Do important stuff that needs to be synchronized  
  25.   
  26. finally:   
  27.     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

  1. fcntl() locks are the most reliable

    Across architectures, they have the best chance of working right on e.g. shared filesystems -- NFS and CIFS mounts, for example.

  2. Most often, lockf() is implemented as "shorthand" for fcntl()

    The other alternative, as "shorthand" for flock(), is possible, but nowadays rare.

  3. fcntl() and flock() have different semantics wrt. inheritance and automatic releases

    fcntl() locks are preserved across an exec(), but not inherited across a fork()[fcntl/lockf的文件锁不会被子进程继承]. The locks are released when the owning process closes any descriptor referring to the same file.