我已经安排了一个cron作业每分钟运行一次,但是有时脚本需要花费一分钟以上的时间才能完成,而且我不希望这些作业彼此开始“堆叠”。我猜这是一个并发问题-即脚本执行需要互斥。

为解决该问题,我让脚本查找了特定文件(“ lockfile.txt”)的存在,并且如果存在,请退出;否则,请退出。但这是一个很糟糕的信号灯!我应该了解一种最佳实践吗?我应该改写一个守护进程吗?

#1 楼

有一些程序可以自动执行此功能,自己消除烦恼和潜在的错误,还可以通过在幕后使用植绒来避免过时的锁定问题(如果您只是使用触摸,则可能会有风险) 。我过去曾经使用过lockrunlckdo,但是现在有flock(1)(在util-linux的最新版本中)很棒。真的很容易使用:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job


评论


由于flock(1)在util-linux中,因此lckdo将被从moreutils中删除。而且该软件包在Linux系统中基本上是必需的,因此您应该能够依靠它的存在。有关用法,请看下面。

– jldugger
2012年4月9日在21:44

是的,羊群现在是我的首选。我什至会更新我的答案以适合。

–womble♦
2012-04-10 6:22

有谁知道flock -n file命令和flock -n file -c命令之间的区别?

–内南
2015年2月5日,14:40

@Nanne,我必须检查代码以确保确定,但是我的有根据的猜测是-c通过外壳程序(根据联机帮助页)运行指定的命令,而“裸露”(非--c)形式只是执行给定的命令。在shell中放一些东西可以使您做类似shell的事情(例如运行多个用;或&&分隔的命令),但是如果您使用的是不受信任的输入,也使您容易受到shell扩展攻击。

–womble♦
2015年2月6日,1:13

这是(假设的)频繁的_cron_job命令的一个参数,试图显示它每分钟都在运行。我删除了它,因为它没有添加任何有用的信息,并引起了混乱(您的经验,如果多年来没有其他人的话)。

–womble♦
17-10-31在8:54

#2 楼

在shell中最好的方法是使用flock(1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock


评论


我不能反对使用fd重定向的棘手问题。太神奇了。

–womble♦
09年9月9日在11:57

在Bash或ZSH中不为我解析,需要消除99和>之间的空格,所以它是99> / ...

–凯尔·勃兰特(Kyle Brandt)
2009年11月9日12:36

@Javier:这并不意味着它不是棘手的和不可思议的,只是它已被记录,棘手和不可思议的。

–womble♦
09年11月11日在12:29

如果在运行时重新启动或以某种方式终止进程,将会发生什么?那会永远锁定吗?

– Alex R
2014年8月22日11:33



我了解此结构会创建一个排他锁,但我不了解如何完成此操作的机制。这个答案中“ 99”的功能是什么?有人在乎解释吗?谢谢!

– ASCII
16年2月10日在9:30

#3 楼

实际上,可以使用flock -n代替lckdo *,因此您将使用内核开发人员提供的代码。

以womble的示例为基础,您将编写类似以下内容的代码:

* * * * * flock -n /some/lockfile command_to_run_every_minute


顺便说一句,请看一下代码,所有flocklockrunlckdo都执行完全相同的操作,因此您可以很容易地使用它。

#4 楼

您尚未指定是否要让脚本等待上一次运行完成。通过“我不希望作业开始彼此“堆叠””,我想您是在暗示脚本要在已经运行的情况下退出,

所以,如果您不想依靠lckdo或类似的工具,您可以执行以下操作:


PIDFILE=/tmp/`basename q4312078q`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "q4312078q already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work



评论


感谢您的示例对您的帮助很有帮助-如果脚本已经运行,我希望它退出。感谢您提及ickdo-它似乎可以解决问题。

–汤姆
09年11月11日上午11:36

FWIW:我喜欢这种解决方案,因为它可以包含在脚本中,因此无论调用脚本的方式如何,锁定均有效。

– David G
18年11月20日在15:36

#5 楼

您可以使用锁定文件。在脚本启动时创建此文件,在脚本结束时将其删除。该脚本在运行其主例程之前,应检查锁定文件是否存在并进行相应的处理。

锁定文件由initscripts以及Unix系统中的许多其他应用程序和实用程序使用。

评论


这是我亲自看到的唯一实现方式。根据维护者的建议,我将其用作OSS项目的镜像

–沃伦
09年11月9日在11:48

#6 楼

现在systemd已经发布,Linux系统上还有另一种调度机制:

systemd.timer


/etc/systemd/system/myjob.service~/.config/systemd/user/myjob.service中:

[Service]
ExecStart=/usr/local/bin/myjob


/etc/systemd/system/myjob.timer~/.config/systemd/user/myjob.timer中:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target


如果下一次激活计时器时服务单元已经处于激活状态,则该服务的另一个实例将不会启动。

另一种方法是,在启动时启动一次作业,每次运行结束后一分钟启动一次作业:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target


#7 楼

这也可能表明您做错了事。如果您的工作运行得如此频繁且如此频繁,也许您应该考虑取消克隆它,并使其成为守护程序风格的程序。

评论


我对此表示不同意。如果您需要定期运行某些程序,则将其设置为守护进程是“坚果的大锤”解决方案。使用锁文件来预防事故是我从未遇到过的一个完美合理的解决方案。

–womble♦
2009年11月9日,11:53

@womble我同意;但是我喜欢用大锤砸坚果! :-)

– wzzrd
2009年11月9日15:01

#8 楼

如果您的cron守护程序的先前实例仍在运行,则不应调用它们。我是一个cron守护程序dcron的开发人员,我们专门尝试防止这种情况。我不知道Vixie cron或其他守护程序如何处理此问题。

#9 楼

我建议使用运行命令-比处理锁简单得多。从文档开始:

运行一是一个包装脚本,运行的脚本最多包含一个带有一组唯一参数的命令的唯一实例。当您一次只需要运行一个副本时,这对于cronjobs通常很有用。

run-this-one与run-one完全一样,不同之处在于它将使用pgrep和kill查找并杀死用户拥有并与目标命令和参数匹配的所有正在运行的进程。请注意,运行这一个将在尝试杀死匹配的进程时阻塞,直到所有匹配的
进程都死了。

运行一恒定地运行与运行一完全相同,除了它
每当COMMAND退出(零或非零)时,都会重现“ COMMAND [ARGS]”。

保持一次运行是恒定运行一次的别名。

运行一直到成功与运行一键式运行完全相同,除了
重新生成“ COMMAND [ARGS]”直到COMMAND成功退出(即,
退出零)。

运行一直到失败的操作与恒定运行一模一样,不同的是
重新生成“ COMMAND [ARGS]”,直到COMMAND失败退出(即,
退出非零值)。 )。

#10 楼

我创建了一个jar来解决这样的问题,例如重复的cron正在运行,可能是java或shell cron。只需在Duplicates.CloseSessions(“ Demo.jar”)中传递cron名称,即可搜索并杀死该cron的existng pid(当前除外)。我已经实现了执行此操作的方法。字符串proname = ManagementFactory.getRuntimeMXBean()。getName();
字符串pid = proname.split(“ @”)[0];
System.out.println(“当前PID:” + pid) ;

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print }' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }


然后再次使用shell命令杀死killid字符串

评论


我认为这并不能真正回答问题。

–卡巴斯德
16 Dec 30 '13:54

#11 楼

无论如何,@ Philip Reynolds的答案将在等待5秒后开始执行代码而没有获得锁定。
以下Flock似乎不起作用,我修改了@Philip Reynolds的答案,对

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock


,这样就不会同时执行代码。
相反,等待5秒钟后,如果当时未获得锁,进程将以1退出。