首先,我们直接使用flock命令看下flock是怎样工作的.
在一个窗口中运行如下命令,其中--verbose会导致flock输出锁文件的详细过程,-n表示non block,也就是如果这个文件无法lock住,进程直接非0退出,而不是一直等待.可以看到正常lock住这个文件了.
#flock --verbose -n test.lock bash -c 'echo lock ok; sleep 100'窗口1中运行的flock命令,成功
同时,在另外一个窗口也敲这条命令,发现lock失败了,这个符合我们的预期.
窗口2中同样的flock命令,失败
为了看到文件锁的详细情况,我们使用lsof来看下第一个窗口flock进程打开的文件,可以看到test.lock这个文件在FD这一列有一个W,表示整个文件被上了一个write lock.所以第二个窗口的flock就无法锁住了
#lsof -p 17997lsof显示窗口1中的flock进程锁住了文件
0x02 在python中使用flockpython中可以使用标准库里面的fcntl.flock来锁文件.我们先尝试用同样的方法来lock一个文件.python进程启动之后直接对/var/run/test.lock1文件进行lock,成功之后运行主程序代码.
有问题的flock代码
然后在两个窗口中运行代码,发现都能跑到主要代码,说明文件没有lock住! 这个是怎么回事?
窗口1中的代码运行
窗口2中的代码运行
使用lsof看下两个进程打开文件的情况,很奇怪,没有发现/var/run/test.lock1! 但是程序的日志显示打开了的啊.
0x03 python使用引用计数进行GC,会自动关闭文件遇到这种奇怪的问题,祭出我们的神器strace对系统调用进行一下追踪.
#strace python3 tflock.py &>/tmp/aa从strace的输出可以看到,/var/run/test.lock1被打开了,然后flock给它上了一个互斥锁,然后就被关闭了.
strace显示,文件lock之后被关闭了
看一下我们的代码,里面没有对close(fd)的调用,为什么文件会被关闭了呢?
python解释器自带GC,而且是引用计数GC.也就是当一个对象的引用计数为0的时候,自动被释放.我们的fd是定义在lockFile1函数中的,当这个函数执行完成,其作用域就没有了,fd对应的open file引用计数变成0,其资源会被释放. 对于open file来说,其释放过程包含了对fd的close调用,这就是为什么代码里面没有close(fd),但是文件被关闭了.
那文件被关闭为什么会导致文件的锁也没有了呢?
在man 2 flock中有如下一段话,flock在显示调用LOCK_UN和所有fd都关闭的情况会被释放.
Furthermore, the lock is released either by an explicit LOCK_UN operation on any of these duplicate file descriptors, or when all such file descriptors have been closed.
0x04 使用全局变量保持文件开启知道原因之后解决这个问题就比较简单了,既然函数中的局部变量会在函数返回之后就没有了,那么用全局变量即可.
给变量fd加上global声明.
正确的flock代码,fd声明global
这样第二个窗口运行的时候直接就报错BlockingIOError了,锁不住文件
第二个窗口文件锁不住了
lsof显示第一个窗口的python3进程成功锁住了文件.
第一个窗口成功锁住文件
0xff 总结flock用来保持一个程序只有一个进程在运行时很有用.但是在python中使用flock一定要注意python对变量作用域的处理,一定要将open函数返回的fd存放在全局变量而不是局部变量中,避免python解释器的GC把文件自动close导致锁失效.