joblib.Memory 是线程安全的吗?

Is joblib.Memory thread-safe for writing?

能否joblib.Memory用于以线程安全的方式写入跨多个进程的公共缓存。在什么情况下,如果有的话,这会失败或导致错误吗?

库首先写入一个临时文件,然后将临时文件移动到目的地。 Source code:

def _concurrency_safe_write(self, to_write, filename, write_func):
    """Writes an object into a file in a concurrency-safe way."""
    temporary_filename = concurrency_safe_write(to_write,
                                                filename, write_func)
    self._move_item(temporary_filename, filename)

写入临时文件在同一操作系统中的进程之间似乎是安全的,因为它在文件名中包含 pid。此外,它在同一进程中的线程之间似乎是安全的,因为它包含线程 ID。 Source:

def concurrency_safe_write(object_to_write, filename, write_func):
    """Writes an object into a unique file in a concurrency-safe way."""
    thread_id = id(threading.current_thread())
    temporary_filename = '{}.thread-{}-pid-{}'.format(
        filename, thread_id, os.getpid())
    write_func(object_to_write, temporary_filename)

    return temporary_filename

将临时文件移动到目标位置时 Windows 出现问题。 Source:

if os.name == 'nt':
    # https://github.com/joblib/joblib/issues/540
    access_denied_errors = (5, 13)
    from os import replace

    def concurrency_safe_rename(src, dst):
        """Renames ``src`` into ``dst`` overwriting ``dst`` if it exists.
        On Windows os.replace can yield permission errors if executed by two
        different processes.
        """
        max_sleep_time = 1
        total_sleep_time = 0
        sleep_time = 0.001
        while total_sleep_time < max_sleep_time:
            try:
                replace(src, dst)
                break
            except Exception as exc:
                if getattr(exc, 'winerror', None) in access_denied_errors:
                    time.sleep(sleep_time)
                    total_sleep_time += sleep_time
                    sleep_time *= 2
                else:
                    raise
        else:
            raise
else:
    from os import replace as concurrency_safe_rename  # noqa

从该源代码中您可以看到,在 Windows 上,它可能会在将临时文件移动到目的地失败后失败,因为在 1 秒的总时间内出现访问被拒绝错误,并且已使用指数重试回退。

相同的源代码有一个 link 问题 #540,它描述了 Windows 错误,并已关闭并附上评论:

Fixed by #541 (hopefully).

评论中的“(希望)”似乎表明作者不能保证修复是最终的,但该问题尚未重新打开,因此可能不会再次发生。

对于其他操作系统,没有特殊逻辑或重试,仅使用标准 os.replace()。描述中提到了它“可能会失败”以及“将是一个原子操作”的情况:

Rename the file or directory src to dst. If dst is a directory, OSError will be raised. If dst exists and is a file, it will be replaced silently if the user has permission. The operation may fail if src and dst are on different filesystems. If successful, the renaming will be an atomic operation (this is a POSIX requirement).

如果没有人在目标目录中更改权限,您应该不太担心此操作失败的可能性。 “if src and dst are on different filesystems”的场景似乎不可行,因为源路径(临时文件)是通过添加一个构建的目标路径的后缀,因此它们应该在同一目录中。

其他关于重命名原子性的问题: