Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/perl/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 线程安全和容错文件写入_Python_File_Thread Safety - Fatal编程技术网

Python 线程安全和容错文件写入

Python 线程安全和容错文件写入,python,file,thread-safety,Python,File,Thread Safety,我有一个长时间运行的过程,在一个文件中写很多东西。结果应该是全部或全部,所以我正在写入一个临时文件,并在最后将其重命名为真实名称。目前,我的代码如下: filename = 'whatever' tmpname = 'whatever' + str(time.time()) with open(tmpname, 'wb') as fp: fp.write(stuff) fp.write(more stuff) if os.path.exists(filename):

我有一个长时间运行的过程,在一个文件中写很多东西。结果应该是全部或全部,所以我正在写入一个临时文件,并在最后将其重命名为真实名称。目前,我的代码如下:

filename = 'whatever'
tmpname = 'whatever' + str(time.time())

with open(tmpname, 'wb') as fp:
    fp.write(stuff)
    fp.write(more stuff)

if os.path.exists(filename):
    os.unlink(filename)
os.rename(tmpname, filename)
with RenamedTemporaryFile('whatever') as f:
    f.write('stuff')
我对此不满意,原因有几个:

  • 如果发生异常,它不会正确清理
  • 它忽略了并发性问题
  • 它是不可重用的(我需要在我的程序中的不同位置使用它)
对如何改进我的代码有什么建议吗?有没有可以帮助我的库?

您可以在写入文件时使用锁定文件。任何后续锁定它的尝试都将被阻止,直到上一个进程/线程的锁被释放为止

from lockfile import FileLock
with FileLock(filename):
    #open your file here....

这样,您就可以避免并发问题,并且在出现异常时不必清理任何遗留文件。

您可以使用Python的
tempfile
模块为您提供一个临时文件名。它可以以线程安全的方式创建临时文件,而不是使用
time.time()
创建临时文件,如果在多个线程中同时使用,该文件可能返回相同的名称

正如在对您的问题的评论中所建议的,这可以与使用上下文管理器相结合。通过查看Python
tempfile.py
源代码,您可以了解如何实现您想要做的事情

下面的代码段可以执行您想要的操作。它使用从
tempfile
返回的一些对象的内部结构

  • 创建临时文件是线程安全的
  • 成功完成后重命名文件是原子的,至少在Linux上是这样。在
    os.path.exists()
    os.rename()
    之间没有单独的检查,这可能会引入竞争条件。对于Linux上的原子重命名,源文件和目标文件必须位于同一文件系统上,这就是为什么此代码将临时文件放置在与目标文件相同的目录中
  • 在大多数情况下,
    RenamedTemporaryFile
    类的行为应类似于
    NamedTemporaryFile
    ,除非使用上下文管理器关闭该类,否则会重命名该文件
样本:

import tempfile
import os

class RenamedTemporaryFile(object):
    """
    A temporary file object which will be renamed to the specified
    path on exit.
    """
    def __init__(self, final_path, **kwargs):
        tmpfile_dir = kwargs.pop('dir', None)

        # Put temporary file in the same directory as the location for the
        # final file so that an atomic move into place can occur.

        if tmpfile_dir is None:
            tmpfile_dir = os.path.dirname(final_path)

        self.tmpfile = tempfile.NamedTemporaryFile(dir=tmpfile_dir, **kwargs)
        self.final_path = final_path

    def __getattr__(self, attr):
        """
        Delegate attribute access to the underlying temporary file object.
        """
        return getattr(self.tmpfile, attr)

    def __enter__(self):
        self.tmpfile.__enter__()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.tmpfile.delete = False
            result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)
            os.rename(self.tmpfile.name, self.final_path)
        else:
            result = self.tmpfile.__exit__(exc_type, exc_val, exc_tb)

        return result
然后您可以这样使用它:

filename = 'whatever'
tmpname = 'whatever' + str(time.time())

with open(tmpname, 'wb') as fp:
    fp.write(stuff)
    fp.write(more stuff)

if os.path.exists(filename):
    os.unlink(filename)
os.rename(tmpname, filename)
with RenamedTemporaryFile('whatever') as f:
    f.write('stuff')

在写入过程中,内容将转到临时文件,退出时文件将被重命名。这段代码可能需要一些调整,但总体思路应该有助于您开始。

带有的
结构对于退出时的清理非常有用,但对于您想要的提交/回滚系统则不适用。可以使用try/except/else块进行此操作

您还应该使用标准方法创建临时文件名,例如使用tempfile模块

记住

以下是完整的修改代码:

import time, os, tempfile

def begin_file(filepath):
    (filedir, filename) = os.path.split(filepath)
    tmpfilepath = tempfile.mktemp(prefix=filename+'_', dir=filedir)
    return open(os.path.join(filedir, tmpfilepath), 'wb') 

def commit_file(f):
    tmppath = f.name
    (filedir, tmpname) = os.path.split(tmppath)
    origpath = os.path.join(filedir,tmpname.split('_')[0])

    os.fsync(f.fileno())
    f.close()

    if os.path.exists(origpath):
        os.unlink(origpath)
    os.rename(tmppath, origpath)

def rollback_file(f):
    tmppath = f.name
    f.close()
    os.unlink(tmppath)


fp = begin_file('whatever')
try:
    fp.write('stuff')
except:
    rollback_file(fp)
    raise
else:
    commit_file(fp)

要可靠地将全部或全部写入文件,请执行以下操作:

import os
from contextlib import contextmanager
from tempfile   import NamedTemporaryFile

if not hasattr(os, 'replace'):
    os.replace = os.rename #NOTE: it won't work for existing files on Windows

@contextmanager
def FaultTolerantFile(name):
    dirpath, filename = os.path.split(name)
    # use the same dir for os.rename() to work
    with NamedTemporaryFile(dir=dirpath, prefix=filename, suffix='.tmp') as f:
        yield f
        f.flush()   # libc -> OS
        os.fsync(f) # OS -> disc (note: on OSX it is not enough)
        f.delete = False # don't delete tmp file if `replace()` fails
        f.close()
        os.replace(f.name, name)
另见(所提及)

用法 要实现missing,可以在Windows上调用(通过Win32 File或ctypes模块)

如果有多个线程,您可以从中调用
queue.put(data)
不同线程,并在专用线程中写入文件:

 for data in iter(queue.get, None):
     file.write(data)
queue.put(无)
中断循环

作为替代,您可以使用锁(线程、多处理、, 要同步访问,请执行以下操作:

def write(self, data):
    with self.lock:
        self.file.write(data)

为了使其可重用,您可以将其转换为上下文管理器。+1表示NamedTemporaryFile和上下文管理器。注意:如果不调用
os.fsync()
。请参阅@J.F.Sebastian使用
os.fsync()
可能会让您更接近容错系统,但
fsync()
仍然不能保证数据在磁盘上,特别是对于具有写缓存的存储。我可能会根据应用程序选择使用/不使用
fsync()
。有一些有趣的信息。飞利浦:OP专门询问容错,而不是最快的方法。但是大多数实现都做了一些有用的事情。在您的链接中:“某些文件系统可能会对元数据写入进行排序,以便将重命名写入磁盘,但新文件的内容尚未在磁盘上。如果我们在此时崩溃,则会在装载时检测到这一点,并将文件截断为0字节。调用fsync()可确保不会发生这种情况。[ext4]”。尽管在
rename()中
case。谢谢,这很有帮助!J.F.是对的,在这种情况下,我不太关心性能,所以一个额外的函数调用就可以了。
fsync
链接+1。注意:在
os.fsync(f)
之前,您可能需要
f.flush()
+1锁定文件链接。注意:对于同一进程中的线程,
线程。可以使用Lock
(或
多处理。Lock
用于通过多处理模块创建的处理)。谢谢,该模块看起来非常有用。谢谢,非常有用的东西。一些需要学习的新概念(试图让我了解
@contextmanager
是如何工作的)。@thg435:yield
语句的目的/优点是什么?@Batman:contextmanager装饰师。阅读its文档以了解原因。然后单击我之前评论中的链接