使用cron定时运行Python脚本,但仅在脚本未运行时才执行

19

我需要每分钟运行一个Python脚本(job.py)。如果该脚本已经在运行,它不能被再次启动。脚本的执行时间可能在10秒到数小时之间。

因此,我将以下内容添加到我的crontab中:

* * * * * root cd /home/lorenzo/cron && python -u job.py 1>> /var/log/job/log 2>> /var/log/job/err
为了避免在脚本已经运行时再次启动它,我使用flock()函数。
以下是脚本(job.py)的内容:
import fcntl
import time
import sys

def doIncrediblyImportantThings ():
    for i in range (100):
        sys.stdout.write ('[%s] %d.\n' % (time.strftime ('%c'), i) )
        time.sleep (1)

if __name__ == '__main__':
    f = open ('lock', 'w')
    try: fcntl.lockf (f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except:
        sys.stderr.write ('[%s] Script already running.\n' % time.strftime ('%c') )
        sys.exit (-1)
    doIncrediblyImportantThings ()

这种方法似乎可行。

我是否遗漏了什么?使用这种方法会遇到任何问题吗?

实现这种行为的更建议或“合适”的方法有哪些?

感谢您提出的任何建议。

3个回答

15

我唯一的建议是让你的异常处理更具体化。你不希望有一天意外删除fcntl导入时隐藏了所产生的NameError。总是尝试捕获你想要处理的最具体异常。在这种情况下,我建议使用以下代码:

import errno

try:
    fcntl.lock(...)
except IOError, e:
    if e.errno == errno.EAGAIN:
        sys.stderr.write(...)
        sys.exit(-1)
    raise

这样,任何导致无法获得锁的其他原因都会显示出来(可能会显示在您的电子邮件中,因为您正在使用cron),您可以决定是否需要管理员查看,程序处理另一个案例,还是其他原因。


1
对于使用Python 3的用户:自Python 3.3以来,fcntl使用OSError而不是IOError。 - R.M.

3

当机器重新启动或在运行脚本时冻结(因此出现活动锁)时,您将遇到麻烦。对抗这种情况的简单方法是使用@reboot cron时间戳来运行rm /path/to/lock


非常感谢。我会考虑这个建议的。文件锁是否在重新启动后仍然存在?这取决于我使用的文件系统(实际上是ext4)吗? - Hyperboreus
2
文件锁在重新启动后不会持久化。它们甚至在进程重新启动时也不会持久化,这就是为什么您不必在代码中释放锁定 - 当进程终止时,锁定会被释放。 - Jean-Paul Calderone
@Jean-Paul 这意味着我不必像Mel所说的那样担心重新启动和冻结吗? - Hyperboreus

2
上周我遇到了这个确切的问题,虽然我找到了一些好的解决方案,但我决定制作一个非常简单和干净的Python包,并将其上传到PyPI。

安装:pip install quicklock

使用它非常简单:

[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> # Let's create a lock so that only one instance of a script will run
...
>>> singleton('hello world')
>>>
>>> # Let's try to do that again, this should fail
...
>>> singleton('hello world')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nate/live/gallery/env/lib/python2.7/site-packages/quicklock/quicklock.py", line 47, in singleton
    raise RuntimeError('Resource <{}> is currently locked by <Process {}: "{}">'.format(resource, other_process.pid, other_process.name()))
RuntimeError: Resource <hello world> is currently locked by <Process 24801: "python">
>>>
>>> # But if we quit this process, we release the lock automatically
...
>>> ^D
[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> singleton('hello world')
>>>
>>> # No exception was thrown, we own 'hello world'!

请看:

https://pypi.python.org/pypi/quicklock


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接