shell命令flock通过加锁方式实现互斥访问。常用于多进程间互斥访问。flock用flock(2)系统调用实现。

Linux提供了flock(对整个文件加锁)、fcntl(对整个文件区域加锁)两个函数来做进程间的文件同步。

同时也可以使用信号量来完成所需的同步,但通常使用文件锁会更好一些,因为内核能够自动将锁与文件关联起来。

flock 是对于整个文件的建议性锁。也就是说,如果一个进程在一个文件(inode)上放了锁,那么其它进程是可以知道的。(建议性锁不强求进程遵守。)

最棒的一点是,flock(2)第一个参数是文件描述符,在此文件描述符关闭时,锁会自动释放。而当进程终止时,所有的文件描述符均会被关闭。于是,很多时候就不用考虑解锁的事情。

用法



flock [-sxon] [-w timeout] lockfile [-c] command...

flock [-sxon] [-w timeout] lockdir [-c] command...

flock [-sxun] [-w timeout] fd


参数

-s: 获取共享锁,有时被称为只读锁。在定向为某文件的FD上设置共享锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置独占锁的请求失败,而其他进程试图在定向为此文件的FD上设置共享锁的请求会成功。

-x,-e:获取互斥锁,有时称为写锁,默认。在定向为某文件的FD上设置独占锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置共享锁或独占锁都会失败。

-u:释放锁。这个不是必须的,当文件关闭时(进程结束后,进程文件都被关闭)锁被自动释放。但是,特殊情况下是必须的,例如受锁保护的命令行fork一个后台进程,此进程不应该持有锁。

Drop  a  lock.   This is usually not required, since a lock is automatically dropped when the

file is closed.  However, it may be required in special cases, for example  if  the  enclosed

command group may have forked a background process which should not be holding the lock.

-n:非阻塞,获取锁失败后立刻返回,退出码1。

-w:seconds,等待超时。

-o:在执行命令前,关闭被锁文件的文件描述符。这是有用的,当时命令行fork一个子进程,该子进程不应该持有锁。

-c:传递一个单独command给shell(在新shell进程中执行命令)。-c选项表示,如果成功锁定,则执行其后用双引号括起的命令,如果是多个命令,可以用分号分隔。

       flock file -c command 等价于flock file sh -c command,前者flock用-c创建进程执行command,后者flock执行sh命令,sh命令创建进程执行命令command。

描述

该命令管理flock(2) locks为shell scripts或the command line。

第一或第二种形式在命令执行时回绕锁。它锁住指定文件或路径(若不存在创建它)。

第三种形式在脚本中应用方便,形式如下



(
flock -n 9 || exit 1
# ... commands executed under lock ...
) 9>/var/lock/mylockfile


The mode used to open the file doesn't matter to flock; using > or >> allows the lockfile to be created if it does not already exist,however, write permission is required.

using < requires that  the file already exists but only read permission is required.

默认情况下,flock会阻塞等,直到获取锁。

flock(2)中描述

Locks  created  by  flock() are associated with an open file table entry.  This means that duplicate

       file descriptors (created by, for example, fork(2) or dup(2)) refer to the same lock, and this  lock

       may  be  modified  or  released  using  any of these descriptors.  Furthermore, the lock is released

       either by an explicit LOCK_UN operation on any of these duplicate  descriptors,  or  when  all  such

       descriptors have been closed.

如果通过一个特定的文件描述符获取了一个锁并且创建了该描述符的一个或多个副本,那么,如果不显示的调用一个解锁操作,只有当文件描述符副本都被关闭了之后锁才会被释放。

通过fork()创建的锁在exec()中会得以保留(除非在文件描述符上设置了close-on-exec标记并且该文件描述符是最后一个引用底层的打开文件描述的描述符)。

应用

1 crontab运用flock防止重复执行



* * * * * (flock -xn ./test.lock -c "sh /root/test.sh") #-n 为非阻塞模式


2 机器down机自动启动或重启

可以在daemon开始的时候, 打开一个文件然后获取一个写锁. 守护脚本也打开文件并设置写锁, 然后阻塞, 一旦写锁获得成功, 则说明daemon已经挂了. 此时守护脚本重启daemon并放弃写锁.



flock -x ./test.lock -c "/usr/local/nginx/sbin/nginx" #去掉-n表示使用阻塞模式


运行中...     再次执行



flock -x ./test.lock -c "/usr/local/nginx/sbin/nginx" #去掉-n表示使用阻塞模式


阻塞中...

flock_共享锁

模拟down机 



[root@localhost ~]# ps aux |grep "nginx"|grep"master"|grep -v "grep"|awk'{print $2}'|xargskill -9


kill后阻塞的命令马上执行 新的进程PID立马产生。

3. flock执行多条命令



#!/bin/sh

echo "pid is $$"

flock wang sh -c " echo $$ ; \
while [ 1 ] ;\
do \
echo wangt ;\
echo $$ ;\
sleep 1; \
done ; "

echo "exec over"
exit 0


该进程执行会产生同时存在的三个进程:1)脚本本身启动进程;2)执行flock时,flock语句本身作为子进程运行,父进程阻塞等待flock进程结束;

3)flock的参数sh开启一个子进程执行后面的命令(有无sh都会开启新进程执行命令)。

1)进程阻塞等待2)进程;2)进程阻塞等待3)进程;3)进程永不退出,所以一直打印进程号(注意,打印的进程号为1)进程号,即$$都为1)进程号),不会打印“exec over”。

若此时该脚本再被调用执行,则会同时再出现两个进程:1)和2),进程2)忙等文件锁(获取锁后才执行后面的命令)。

flock第三种形式应用

flock可以接收文件描述符参数。这种方法在 shell 脚本里特别有用。比如如下代码:



lockit () {
exec 7<>.lock
flock -n 7 || {
echo "Waiting for lock to release..."
flock 7
}
}


​exec​​​行打开​​.lock​​​文件为7号文件描述符,然后拿 flock 尝试锁它。如果失败了,就输出一条消息,并且等待锁被释放。那个7号文件描述符就让它一直开着了,反正脚本执行完毕内核会释放,也不用去调用​​trap​​内建命令了。

上边有一点很有意思的是,flock 是一个子进程,但是因为文件描述符在 fork 和 execve 中会共享,而 flock 锁在 fork 和 execve 时也不会改变,所以子进程在那个文件描述符上加锁后,即使它退出了,因为那个文件描述符父进程还有一份,所以不会被关闭,锁也就得以保留。(所以,如果余下的脚本里要是有进程带着那个文件描述符fork 到后台锁就不会在脚本执行完后自动解除啦……)

----锁不会被释放



#!/bin/bash

{
flock –n 3
[$? –eq 1] && { echo fail ;exit}
echo succeed
#Doyour task here …

} 3 <> mylockfile


首先使用<>打开mylockfile 并定向到文件描述符3,而定向文件描述符是先于命令执行的,因此假如要执行的语句段中需要读写mylockfile文件,例如想获得上一个脚本实例的 pid,并将此次的脚本实例的pid写入mylockfile。此时直接用>打开mylockfile会清空上次存入的内容,而用<打开 mylockfile当它不存在时会导致一个错误。

其次,以非阻塞的方式打开mylockfile,flock –n 3,其中3是mylockfile的文件描述符,若能够正常获取锁(默认是排它锁),则继续往下执行,否则返回1,该返回码能够在接下来的语句中[ $? –eq 1 ] 进行判断,若为1,则输出失败并退出当前脚本。