寻找可靠的 python 进程同步技术(Linux 不可移植)
In search of reliable python process synchronization techniques (Linux non-portable)
[我在 Linux 上使用 python 3 实现 thread-/process-safe 获取文件锁的解决方案非常困难(我不关心便携式解决方案,因为我正在开发的程序广泛使用了 Linux-kernel-exclusive-containerization 技术。]
看完http://apenwarr.ca/log/?m=201012#13后,我决定使用fcntl.lockf()
锁定一个文件以进行进程独占访问,并编写了以下函数:
import contextlib as Contextlib
import errno as Errno
import fcntl as Fcntl
import os as Os
@Contextlib.contextmanager
def exclusiveOpen(filename,
mode):
try:
fileDescriptor = Os.open(filename,
Os.O_WRONLY | Os.O_CREAT)
except OSError as e:
if not e.errno == Errno.EEXIST:
raise
try:
Fcntl.lockf(fileDescriptor,
Fcntl.LOCK_EX)
fileObject = Os.fdopen(fileDescriptor,
mode)
try:
yield fileObject
finally:
fileObject.flush()
Os.fdatasync(fileDescriptor)
finally:
Os.close(fileDescriptor)
除此之外,我确定它是不正确的(为什么它在Fcntl.lockf(fileDescriptor, Fcntl.LOCK_EX)
中不阻塞?),最让我感到不安的部分是fileDescriptor
已获取 - 如果该文件不存在,则创建它......但是如果两个进程同时执行此部分,会发生什么情况?是否存在竞争条件的机会,两个线程都尝试创建文件?如果是这样,怎么可能阻止这种情况 - 当然不是使用另一个锁定文件(?),因为它必须以相同的方式创建(?!?!)我迷路了。任何帮助是极大的赞赏。
更新:发布了另一种解决潜在问题的方法。我在这种方法中看到的问题是过程名称不能等于现有 UNIX 域套接字(可能由另一个程序创建)的名称 - 我对此是否正确?
基于 ,我最终使用抽象的 UNIX 套接字而不是锁定文件来同步进程,(如果有人发布更好的答案,我会很乐意接受)。
import contextlib as Contextlib
import os as Os
import socket as Socket
import time as Time
@Contextlib.contextmanager
def locked(name,
timeout):
try:
lock = Socket.socket(Socket.AF_UNIX,
Socket.SOCK_DGRAM)
while True:
try:
lock.bind('[=10=]' + name)
break
except:
if timeout:
Time.sleep(1)
timeout = timeout - 1
else:
raise
yield
except:
raise
finally:
lock.close()
# usage:
with locked("procedureName", 5):
pass # perform actions on shared resource
致反对者:想解释一下为什么您认为这不好吗?
据我所知,创建文件时不可能出现竞争条件。如果两个(或更多)进程或线程试图通过 open
和 O_CREAT
标志而不是 O_EXCL
标志同时创建同一个文件,该文件将被创建一次,并且所有调用者将在同一文件上获得一个打开的文件描述符——这正是您所需要的。我假设您在 Linux 上使用 C-Python,并且您的 Python 代码将使用底层 C open 函数结束。
因此当您的代码执行 lockf
函数时,您在现有文件上有一个打开的文件描述符,因此底层 lockf
调用保证一次只有一个进程可以持有锁,前提是底层文件系统支持锁定。
我在 Unix (FreeBSD) 系统上试过,它按预期运行。
[我在 Linux 上使用 python 3 实现 thread-/process-safe 获取文件锁的解决方案非常困难(我不关心便携式解决方案,因为我正在开发的程序广泛使用了 Linux-kernel-exclusive-containerization 技术。]
看完http://apenwarr.ca/log/?m=201012#13后,我决定使用fcntl.lockf()
锁定一个文件以进行进程独占访问,并编写了以下函数:
import contextlib as Contextlib
import errno as Errno
import fcntl as Fcntl
import os as Os
@Contextlib.contextmanager
def exclusiveOpen(filename,
mode):
try:
fileDescriptor = Os.open(filename,
Os.O_WRONLY | Os.O_CREAT)
except OSError as e:
if not e.errno == Errno.EEXIST:
raise
try:
Fcntl.lockf(fileDescriptor,
Fcntl.LOCK_EX)
fileObject = Os.fdopen(fileDescriptor,
mode)
try:
yield fileObject
finally:
fileObject.flush()
Os.fdatasync(fileDescriptor)
finally:
Os.close(fileDescriptor)
除此之外,我确定它是不正确的(为什么它在Fcntl.lockf(fileDescriptor, Fcntl.LOCK_EX)
中不阻塞?),最让我感到不安的部分是fileDescriptor
已获取 - 如果该文件不存在,则创建它......但是如果两个进程同时执行此部分,会发生什么情况?是否存在竞争条件的机会,两个线程都尝试创建文件?如果是这样,怎么可能阻止这种情况 - 当然不是使用另一个锁定文件(?),因为它必须以相同的方式创建(?!?!)我迷路了。任何帮助是极大的赞赏。
更新:发布了另一种解决潜在问题的方法。我在这种方法中看到的问题是过程名称不能等于现有 UNIX 域套接字(可能由另一个程序创建)的名称 - 我对此是否正确?
基于 ,我最终使用抽象的 UNIX 套接字而不是锁定文件来同步进程,(如果有人发布更好的答案,我会很乐意接受)。
import contextlib as Contextlib
import os as Os
import socket as Socket
import time as Time
@Contextlib.contextmanager
def locked(name,
timeout):
try:
lock = Socket.socket(Socket.AF_UNIX,
Socket.SOCK_DGRAM)
while True:
try:
lock.bind('[=10=]' + name)
break
except:
if timeout:
Time.sleep(1)
timeout = timeout - 1
else:
raise
yield
except:
raise
finally:
lock.close()
# usage:
with locked("procedureName", 5):
pass # perform actions on shared resource
致反对者:想解释一下为什么您认为这不好吗?
据我所知,创建文件时不可能出现竞争条件。如果两个(或更多)进程或线程试图通过 open
和 O_CREAT
标志而不是 O_EXCL
标志同时创建同一个文件,该文件将被创建一次,并且所有调用者将在同一文件上获得一个打开的文件描述符——这正是您所需要的。我假设您在 Linux 上使用 C-Python,并且您的 Python 代码将使用底层 C open 函数结束。
因此当您的代码执行 lockf
函数时,您在现有文件上有一个打开的文件描述符,因此底层 lockf
调用保证一次只有一个进程可以持有锁,前提是底层文件系统支持锁定。
我在 Unix (FreeBSD) 系统上试过,它按预期运行。