通过文件锁 Lockfile/flock 让脚本单实例运行

1、Perl File::Lockfile 模块

用Perl写了一些监控脚本,放在crontab中调度执行。有时候会发现一个脚本运行时间过长,会同时跑起多个实例,因此有必要为脚本加上控制,只运行一个实例。

最简单自然的想法,在脚本中检查并创建一个空的lock文件,脚本结束时再删除。通过判断文件是否存在的方式来判断脚本是否已经运行。不过这样做有个bug,如果脚本运行过程中异常终止,lock文件没有正常删除,就会导致脚本无法再运行。

空的lock文件不行,那么考虑在lock文件中加入一点内容,比如进程的PID号,然后通过检查该PID号的进程是否还在运行,就能避免上述bug了。在CPAN上有很多现成的模块能够完成上述功能,如File::Lockfile, File::Pid, Proc::PID::File 等。

下面是File::Lockfile的一个示例,非常简单:

以下是代码片段:

 

#!/usr/bin/perl -w 
use File::Lockfile; 
# lock文件位于/tmp目录,名为test_file_lock.lck 
my $lockfile = File::Lockfile->new(’test_file_lock’,’/tmp’); 
# 检查脚本是否已经运行,如已运行则退出 
if ( my $pid = $lockfile->check ) { 
  print "program is already running with PID: $pid"; 
  exit; 
} 
#更新lock文件 
$lockfile->write; 
# 脚本逻辑 
sleep 30 
#删除lock文件 
$lockfile->remove;

通过查看File/Lockfile.pm的源代码可以看到,判断lock文件中记录的进程是否已经运行,简单的通过 kill -0 $pid 即可实现。所以即使不用上述模块,自己实现也是非常容易的。

小结:

该方法是在脚本中经常用到限制单实例的方法,MySQL 等程序在每次启动前也会检查上次遗留的 mysql.pid 文件。

另一个方法:给lock文件加排它锁,判断是否有锁来确保唯一性。

其实,第一个方法是有缺陷的,因为 pid 会重用,还有竞态问题没考虑。

2、下面分享个更牛叉的命令:flock

当多个进程可能会对同样的数据执行操作时,这些进程需要保证其它进程没有也在操作,以免损坏数据。

通常,这样的进程会使用一个「锁文件」,也就是建立一个文件来告诉别的进程自己在运行,如果检测到那个文件存在则认为有操作同样数据的进程在工作。这样的问题是,进程不小心意外死亡了,没有清理掉那个锁文件,那么只能由用户手动来清理了。像 pacman 或者 apt-get 一些数据库服务经常在意外关闭时留下锁文件需要用户清理。我以前写了个 pidfile,它会将自己的 pid 写到文件里去,所以,如果启动时文件存在,但是对应的进程不存在,那么它也可以知道没有其它进程要访问它要访问的数据(这里只讨论如何避免数据的并发讨论,不考虑进程意外退出时的数据完整性)。但是,Linux 的 pid 是会复用的。而且,检查 pidfile 也有点麻烦不是么?(还有竞态呢)

某天,我发现了 flock 这个系统调用。flock 是对于整个文件的建议性锁。也就是说,如果一个进程在一个文件(inode)上放了锁,那么其它进程是可以知道的。(建议性锁不强求进程遵守。)最棒的一点是,它的第一个参数是文件描述符,在此文件描述符关闭时,锁会自动释放。而当进程终止时,所有的文件描述符均会被关闭。于是,很多时候就不用考虑解锁的事情啦。

flock 有个对应的 shell 命令也叫 flock,很好用的。使用最广泛的 cronie 这个定时任务服务很笨的,不像小巧的 dcron 那样同一任务不会同时跑多个。于是乎,服务器上经常看到一堆未退出的 cron 任务进程。把所有这样的任务包一层 flock 就不会导致 cronie 启动 N 个进程做同一件事啦:

flock -n /tmp/.my.lock -c 'command to run'
即使是 dcron,有时会有两个操作同一数据的任务,也需要使用 flock 来调度。不过这次不用-n参数让文件被锁住时失败退出了。我们要等拥有锁的进程完事再执行。如下,两个任务(有所修改),一个是从远程同步数据到本地的,另一个是备份同步过来的数据的。同时执行的话,就会备份到不完整的数据了。

*/7 *    * * * ID=syncdata       LANG=zh_CN.UTF-8 flock /tmp/.backingup -c my_backup_script
@daily         ID=backupdata     LANG=zh_CN.UTF-8 [ -d ~/data ] && cd ~/data && nice -n19 ionice -c3 flock /tmp/.backingup -c "tar cJf backup_$(date +"%Y%m%d").tar.xz data_dir --exclude='*~'"
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 到后台锁就不会在脚本执行完后自动解除啦……)

PS: 经我测试,其它一些类 Unix 系统上或者没有 flock 这个系统调用,只有 fcntl 那个,或者行为和 Linux 的不一样。

3、最后介绍下详细的语法:

flock分为两种锁:一种是共享锁,使用-s参数;一种是独享锁,使用-x参数。

flock有两种用法,man page是这么说的:

http://linux.die.net/man/1/flock
Synopsis【语法】
flock [-sxon] [-w timeout] lockfile [-c] command…
flock [-sxon] [-w timeout] lockdir [-c] command…
flock [-sxun] [-w timeout] fd

介绍一下参数:
-s为共享锁,在定向为某文件的FD上设置共享锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置独占锁的请求失败,而其他进程试图在定向为此文件的FD上设置共享锁的请求会成功。
-e为独占或排他锁,在定向为某文件的FD上设置独占锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置共享锁或独占锁都会失败。只要未设置-s参数,此参数默认被设置。
-u手动解锁,一般情况不必须,当FD关闭时,系统会自动解锁,此参数用于脚本命令一部分需要异步执行,一部分可以同步执行的情况。
-n为非阻塞模式,当试图设置锁失败,采用非阻塞模式,直接返回1,并继续执行下面语句。
-w设置阻塞超时,当超过设置的秒数,就跳出阻塞,返回1,并继续执行下面语句。
-o必须是使用第一种格式时才可用,表示当执行command前关闭设置锁的FD,以使command的子进程不保持锁。
-c执行其后的comand。

This utility manages flock(2) locks from within shell scripts or the command line.
The first and second forms wraps the lock around the executing a command, in a manner similar to su(1) or newgrp(1). It locks a specified file or directory, which is created (assuming appropriate permissions), if it does not already exist.

第一种和第二种方式类似于su或者newgrp,锁住一个特定的文件或文件夹,如果该文件或文件夹不存在,会被创建。这种用法用于锁住该文件,然后执行command。
The third form is convenient inside shell scripts, and is usually used the following manner:
第三种方式可以方便的用在shell脚本中,且以以下的方式使用:
(
flock -s 200
# … commands executed under lock …
) 200>/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没有关系,使用>或者>>,如果文件不存在将创建文件,且需要有写权限;<需要该文件已经存在,且有读权限。

By default, if the lock cannot be immediately acquired, flock waits until the lock is available.

默认情况下,如果无法获取锁,flock会等待,直到获取到锁。
man page说的很晦涩,看几个个例子就清楚了:

echo “12345″ >/tmp/test
开两个终端,登录到linux上。
一个终端执行命令1:
flock -x /tmp/test.lock -c “cat /tmp/test”
这个命令立刻会输出test文件的内容
然后,在另一个终端上执行命令2:
( flock -x 200; cat /tmp/test; sleep 30 ) 200<>/tmp/test.lock
这个命令以独享的方式锁住test.lock文件,读出test文件的内容,睡眠30秒;
执行这个命令后,可以立刻看到有输出,并且在sleep;
再执行命令1,可以看到命令1会一直等到命令2结束后,才会输出test的内容。
如果修改命令1,变成以下的方式,命名为命令3:
flock -x -n /tmp/test.lock -c “cat /tmp/test”
然后,先执行命令2,在执行命令3,可以看到命令3立刻执行结束,但没有任何输出,这是因为,-n的方式在获取不到文件锁时,会立刻退出,而不是等待。

最后再举个实用的例子:

#!/bin/bash
{
flock -n 3
[ $? -eq 1 ] && { echo fail; exit; }
echo $$
sleep 10
} 3<>mylockfile

此例的功能为当有一个脚本实例正在执行时,另一个试图执行该脚本的进程会失败退出。
sleep那句可以换成您需要执行的语句段。

这里请注意一点,我使用<>打开mylockfile,原因是定向文件描述符是先于命令执行的。因此假如在您要执行的语句段中需要读写mylockfile文件,例如想获得上一个脚本实例的pid,并将此次的脚本实例的pid写入mylockfile。此时直接用>打开mylockfile会清空上次存入的内容,而用<打开mylockfile当它不存在时会导致一个错误。当然这些问题都可以用其他方法解决,我只是点出这种最通用的方法。

 

4、Refer:

1、flock——Linux 下的文件锁

http://lilydjwg.is-programmer.com/2013/7/26/flock-file-lock-in-linux.40104.html

2、flock文件锁

http://my.oschina.net/leejun2005/admin/edit-blog?blog=108656

3、介绍个linux下在shell脚本中设置锁的命令

http://bbs.chinaunix.net/thread-900417-1-1.html

4、bash并发编程和flock

http://bit.ly/1XngKRF

转载于:https://my.oschina.net/leejun2005/blog/108656

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值