简体   繁体   English

在多个进程中使用 Python flock()

[英]Using Python flock() across multiple processes

I have some legacy code that needs to access the same data file across multiple threads and processes.我有一些遗留代码需要跨多个线程和进程访问相同的数据文件。 I am trying to implement locking to protect the data.我正在尝试实施锁定以保护数据。

Multithreaded多线程

import contextlib
import threading

FILE_PATH = "foo.txt"

USE_GLOBAL_LOCK = False

if USE_GLOBAL_LOCK:
    global_lock = threading.Lock()
else:
    global_lock = contextlib.nullcontext()

def do_write_then_read(results) -> None:
    # Write to disk
    data = "FOO"
    with global_lock:
        with open(FILE_PATH, "w") as f:
            f.write(data)

    # Read from disk
    data = None
    with global_lock:
        with open(FILE_PATH, "r") as f:
            data = f.read()
    results.append(data)

def run_multithreaded() -> None:
    results = []

    threads = []
    for _ in range(10):
        threads.append(threading.Thread(target=do_write_then_read, args=[results]))
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    print(results)

The output is usually correct:输出通常是正确的:

 ['FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO']

but sometimes there is missing data.但有时会丢失数据。 I believe this is due to a race condition between the read() and the write() , which initially truncates the file:我相信这是由于read()write()之间的竞争条件,它最初会截断文件:

 ['', '', 'FOO', '', '', 'FOO', '', 'FOO', 'FOO', 'FOO']`

Setting USE_GLOBAL_LOCK to protect the file access using a threading.Lock indeed fixes the problem.设置USE_GLOBAL_LOCK以使用threading.Lock保护文件访问。Lock 确实解决了这个问题。

Multithreaded and multiprocess多线程和多进程

However, running this across multiple processes again results in missing data.但是,再次跨多个进程运行会导致数据丢失。

Here's some test code that forks subprocesses that each invoke the above run_multithreaded() method.下面是一些测试代码,它派生出每个调用上述run_multithreaded()方法的子进程。

import fcntl
import subprocess

def run_multiprocess() -> None:
    processes = []
    for _ in range(3):
        CMD = "python3 -c 'import foo; foo.run_multithreaded()'"
        processes.append(subprocess.Popen(CMD, shell=True))
    for p in processes:
        p.wait()

Output with missing data:缺少数据的输出:

['', '', 'FOO', '', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO']
['', '', '', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO']
['FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO', 'FOO']

Here we redefine do_write_then_read() to add filesystem-based locks ( flock ) so that the file can be locked across multiple processes:在这里,我们重新定义do_write_then_read()以添加基于文件系统的锁( flock ),以便可以跨多个进程锁定文件:

def do_write_then_read(results) -> None:
    # write
    data = "FOO"
    with global_lock:
        with open(FILE_PATH, "w") as f:
            # Acquire file lock
            fd = f.fileno()
            fcntl.flock(fd, fcntl.LOCK_EX)

            f.write(data)
            # Exiting the context closes file and releases lock

    # read
    data = None
    with global_lock:
        with open(FILE_PATH, "r") as f:
            # Acquire file lock
            fd = f.fileno()
            fcntl.flock(fd, fcntl.LOCK_EX)

            data = f.read()
            # Exiting the context closes file and releases lock
    results.append(data)

However, this doesn't fix the problem, and I can't figure out why, no matter what I try :P但是,这并不能解决问题,无论我尝试什么,我都不知道为什么:P

I'm on Mac / Linux with Python 3.9.我在 Mac / Linux 上使用 Python 3.9。

As VPfB noted, you have to postpone the truncation until after you acquire the lock.正如 VPfB 所指出的,您必须将截断推迟到获得锁之后。 You can just replace threading.Lock with multiprocessing.Lock .您可以将threading.Lock替换为multiprocessing.Lock Depending on how the processes get spawned, sharing a lock may be more or less easy .根据进程是如何产生的,共享锁可能或多或少容易

Alternatively, you could use a separate lock file.或者,您可以使用单独的锁定文件。

with open(FILE_PATH + ".lock", "w") as lockfile:
    fcntl.flock(lockfile.fileno(), fcntl.LOCK_EX)
    with open(FILE_PATH, "w") as f:
        f.write(data)

with open(FILE_PATH + ".lock", "w") as lockfile:
    fcntl.flock(lockfile.fileno(), fcntl.LOCK_SH)
    with open(FILE_PATH, "r") as f:
        data = f.read()

There is no good way to remove the lock file unless you know all processes have left this code section.除非您知道所有进程都已离开此代码段,否则没有删除锁定文件的好方法。 We could come up with more complicated schemes but at that point switching to the multiprocessing module is probably easier.我们可以提出更复杂的方案,但此时切换到多处理模块可能更容易。

The problem is that the global locks are shared in multiprocessing in all threads (ie the threads will literally access the same lock across the multiple threads), but you have to explicitly pass them to the processes in multiprocessing, as you'll end up with copies here that are otherwise not connected and live in different process spaces.问题是全局锁在所有线程的多处理中共享(即线程实际上将跨多个线程访问相同的锁),但是您必须明确地将它们传递给多处理中的进程,因为您最终会得到此处的副本以其他方式未连接并位于不同的进程空间中。

See this example from the docs and for more information on that issue.请参阅文档中的此示例以及有关该问题的更多信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM