当多个进程尝试同时写入文件然后从文件读取时如何防止竞争条件
How to prevent a race condition when multiple processes attempt to write to and then read from a file at the same time
我有以下代码(为清楚起见进行了简化):
import os
import errno
import imp
lib_dir = os.path.expanduser('~/.brian/cython_extensions')
module_name = '_cython_magic_5'
module_path = os.path.join(lib_dir, module_name + '.so')
code = 'some code'
have_module = os.path.isfile(module_path)
if not have_module:
pyx_file = os.path.join(lib_dir, module_name + '.pyx')
# THIS IS WHERE EACH PROCESS TRIES TO WRITE TO THE FILE. THE CODE HERE
# PREVENTS A RACE CONDITION.
try:
fd = os.open(pyx_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
else:
os.fdopen(fd, 'w').write(code)
# THIS IS WHERE EACH PROCESS TRIES TO READ FROM THE FILE. CURRENTLY THERE IS A
# RACE CONDITION.
module = imp.load_dynamic(module_name, module_path)
(以上部分代码借鉴自this answer。)
当多个进程同时 运行 时,此代码只会导致一个进程打开并写入 pyx_file
(假设 pyx_file
尚不存在)。问题是,当这个进程正在写入 pyx_file
时,其他进程尝试加载 pyx_file
——在后面的进程中会出现错误,因为在它们读取 pyx_file
时,它是不完整。 (具体来说,引发了 ImportError
,因为进程正在尝试导入文件的内容。)
避免这些错误的最佳方法是什么?一种想法是让进程在 while 循环中不断尝试导入 pyx_file
,直到导入成功。 (这个解决方案似乎不是最优的。)
每次访问文件时使用PID一个空文件来锁定。
用法示例:
from mercurial import error, lock
try:
l = lock.lock("/tmp/{0}.lock".format(FILENAME), timeout=600) # wait at most 10 minutes
# do something
except error.LockHeld:
# couldn't take the lock
else:
l.release()
来源:Python: module for creating PID-based lockfile?
这会给你一个大概的概念。 OO、vim等应用中使用了该方法。
实现的方法是每次打开都拿一个独占锁。写入者在写入数据时持有锁,而 reader 会阻塞,直到写入者使用 fdclose 调用释放锁。如果文件已经部分写入并且写入过程异常退出,这当然会失败,所以如果无法加载模块,应该显示删除文件的适当错误:
import os
import fcntl as F
def load_module():
pyx_file = os.path.join(lib_dir, module_name + '.pyx')
try:
# Try and create/open the file only if it doesn't exist.
fd = os.open(pyx_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY):
# Lock the file exclusively to notify other processes we're writing still.
F.flock(fd, F.LOCK_EX)
with os.fdopen(fd, 'w') as f:
f.write(code)
except OSError as e:
# If the error wasn't EEXIST we should raise it.
if e.errno != errno.EEXIST:
raise
# The file existed, so let's open it for reading and then try and
# lock it. This will block on the LOCK_EX above if it's held by
# the writing process.
with file(pyx_file, "r") as f:
F.flock(f, F.LOCK_EX)
return imp.load_dynamic(module_name, module_path)
module = load_module()
我有以下代码(为清楚起见进行了简化):
import os
import errno
import imp
lib_dir = os.path.expanduser('~/.brian/cython_extensions')
module_name = '_cython_magic_5'
module_path = os.path.join(lib_dir, module_name + '.so')
code = 'some code'
have_module = os.path.isfile(module_path)
if not have_module:
pyx_file = os.path.join(lib_dir, module_name + '.pyx')
# THIS IS WHERE EACH PROCESS TRIES TO WRITE TO THE FILE. THE CODE HERE
# PREVENTS A RACE CONDITION.
try:
fd = os.open(pyx_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
else:
os.fdopen(fd, 'w').write(code)
# THIS IS WHERE EACH PROCESS TRIES TO READ FROM THE FILE. CURRENTLY THERE IS A
# RACE CONDITION.
module = imp.load_dynamic(module_name, module_path)
(以上部分代码借鉴自this answer。)
当多个进程同时 运行 时,此代码只会导致一个进程打开并写入 pyx_file
(假设 pyx_file
尚不存在)。问题是,当这个进程正在写入 pyx_file
时,其他进程尝试加载 pyx_file
——在后面的进程中会出现错误,因为在它们读取 pyx_file
时,它是不完整。 (具体来说,引发了 ImportError
,因为进程正在尝试导入文件的内容。)
避免这些错误的最佳方法是什么?一种想法是让进程在 while 循环中不断尝试导入 pyx_file
,直到导入成功。 (这个解决方案似乎不是最优的。)
每次访问文件时使用PID一个空文件来锁定。
用法示例:
from mercurial import error, lock
try:
l = lock.lock("/tmp/{0}.lock".format(FILENAME), timeout=600) # wait at most 10 minutes
# do something
except error.LockHeld:
# couldn't take the lock
else:
l.release()
来源:Python: module for creating PID-based lockfile?
这会给你一个大概的概念。 OO、vim等应用中使用了该方法。
实现的方法是每次打开都拿一个独占锁。写入者在写入数据时持有锁,而 reader 会阻塞,直到写入者使用 fdclose 调用释放锁。如果文件已经部分写入并且写入过程异常退出,这当然会失败,所以如果无法加载模块,应该显示删除文件的适当错误:
import os
import fcntl as F
def load_module():
pyx_file = os.path.join(lib_dir, module_name + '.pyx')
try:
# Try and create/open the file only if it doesn't exist.
fd = os.open(pyx_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY):
# Lock the file exclusively to notify other processes we're writing still.
F.flock(fd, F.LOCK_EX)
with os.fdopen(fd, 'w') as f:
f.write(code)
except OSError as e:
# If the error wasn't EEXIST we should raise it.
if e.errno != errno.EEXIST:
raise
# The file existed, so let's open it for reading and then try and
# lock it. This will block on the LOCK_EX above if it's held by
# the writing process.
with file(pyx_file, "r") as f:
F.flock(f, F.LOCK_EX)
return imp.load_dynamic(module_name, module_path)
module = load_module()