关于Python:如何在多个进程尝试写入然后同时从文件读取时防止竞争条件

How to prevent a race condition when multiple processes attempt to write to and then read from a file at the same time

我有以下代码(为了清楚起见,简化了代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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)

(上面的一些代码是从这个答案中借用的。)

当同时运行多个进程时,此代码只会导致一个进程打开并写入pyx_file(假设pyx_file不存在)。问题是,当这个进程正在写入pyx_file时,其他进程试图加载pyx_file--后一个进程中会出现错误,因为在读取pyx_file时,它是不完整的。(具体来说,由于进程试图导入文件的内容,因此会引发ImportError。)

避免这些错误的最佳方法是什么?一种想法是让进程在一段时间内一直尝试导入pyx_file,直到导入成功为止。(此解决方案似乎不太理想。)


这样做的方法是每次打开一个专用锁。写入程序在写入数据时持有锁,而读卡器将一直阻塞,直到写入程序使用fdclose调用释放锁为止。当然,如果文件已部分写入并且写入过程异常退出,这将失败,因此,如果模块无法加载,则应显示删除文件的适当错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()


每次访问文件时,使用pid->strike>an empty file to lock every you access a file.

示例用法:

1
2
3
4
5
6
7
8
9
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:用于创建基于pid的锁文件的模块?

这会给你一个大概的想法。这种方法被用于OO、VIM和其他应用中。