fcntl.flock - 如何实现超时?

13 投票
5 回答
11496 浏览
提问于 2025-04-16 13:24

我正在使用 Python 2.7。

我想创建一个包装函数,围绕 fcntl.flock(),这个函数会在设定的时间后超时:

wrapper_function(timeout):

我尝试在另一个线程中调用它,并使用 thread.join(timeout),但似乎 fcntl.flock() 还是一直在阻塞:

def GetLock(self, timeout):
    """Returns true if lock is aquired, false if lock is already in use"""
    self.__lock_file = open('proc_lock', 'w')

    def GetLockOrTimeOut():
        print 'ProcessLock: Acquiring Lock'            
        fcntl.flock(self.__lock_file.fileno(), fcntl.LOCK_EX)
        print 'ProcessLock: Lock Acquired'

    thread = threading.Thread(target=GetLockOrTimeOut)
    thread.start()
    thread.join(timeout)

    if thread.isAlive():
        print 'GetLock timed out'
        return False
    else:
        return True

我查了一些关于终止线程的解决方案,最流行的方法似乎是继承 threading.thread 类,并添加一个功能来在线程中引发异常。不过,我发现一个 链接,说这种方法对本地调用不起作用,而我很确定 fcntl.flock() 是在调用一个本地函数。有什么建议吗?

背景:我正在使用文件锁来创建一个单实例应用程序,但我不想让第二个实例在第一个实例结束之前一直挂着。

5 个回答

5

对于Python 3.5及以上版本,Glenn Maynard的解决方案已经不再有效,这是因为PEP-475的影响。下面是一个修改过的版本:

import signal, errno
from contextlib import contextmanager
import fcntl

@contextmanager
def timeout(seconds):
    def timeout_handler(signum, frame):
        # Now that flock retries automatically when interrupted, we need
        # an exception to stop it
        # This exception will propagate on the main thread, make sure you're calling flock there
        raise InterruptedError

    original_handler = signal.signal(signal.SIGALRM, timeout_handler)

    try:
        signal.alarm(seconds)
        yield
    finally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, original_handler)

with timeout(1):
    f = open("test.lck", "w")
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
    except InterruptedError:
        # Catch the exception raised by the handler
        # If we weren't raising an exception, flock would automatically retry on signals
        print("Lock timed out")
11

我相信有很多方法可以解决这个问题,不过我们可以试试用一种非阻塞的锁。也就是说,尝试几次后,如果还没成功,就放弃吧。

要使用非阻塞锁,你需要加上一个叫做 fcntl.LOCK_NB 的标志,像这样:

fcntl.flock(self.__lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
27

系统调用的超时是通过信号来实现的。当大多数阻塞的系统调用遇到信号时,会返回一个叫做EINTR的错误码,所以你可以使用alarm来设置超时。

下面是一个上下文管理器,它可以与大多数系统调用一起使用。如果某个阻塞的系统调用花费的时间太长,就会引发IOError错误。

import signal, errno
from contextlib import contextmanager
import fcntl

@contextmanager
def timeout(seconds):
    def timeout_handler(signum, frame):
        pass

    original_handler = signal.signal(signal.SIGALRM, timeout_handler)

    try:
        signal.alarm(seconds)
        yield
    finally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, original_handler)

with timeout(1):
    f = open("test.lck", "w")
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
    except IOError, e:
        if e.errno != errno.EINTR:
            raise e
        print "Lock timed out"

撰写回答