Python多进程/多线程加速文件复制

Python多进程/多线程加速文件复制,python,multithreading,shutil,Python,Multithreading,Shutil,我有一个程序可以将大量文件从一个位置复制到另一个位置——我说的是100000多个文件(我现在正在复制314g的图像序列)。它们都在巨大、非常快速的网络存储上,RAID的速度非常之快。我正在使用shutil按顺序复制文件,这需要一些时间,所以我正在尝试找到最佳的方法来优化它。我注意到我使用的一些软件可以有效地从网络上多线程读取文件,从而大大缩短了加载时间,所以我想尝试用python来实现这一点 我没有编程多线程/多进程的经验-这似乎是正确的继续领域吗?如果是这样的话,最好的方法是什么?我看过其他一

我有一个程序可以将大量文件从一个位置复制到另一个位置——我说的是100000多个文件(我现在正在复制314g的图像序列)。它们都在巨大、非常快速的网络存储上,RAID的速度非常之快。我正在使用shutil按顺序复制文件,这需要一些时间,所以我正在尝试找到最佳的方法来优化它。我注意到我使用的一些软件可以有效地从网络上多线程读取文件,从而大大缩短了加载时间,所以我想尝试用python来实现这一点


我没有编程多线程/多进程的经验-这似乎是正确的继续领域吗?如果是这样的话,最好的方法是什么?我看过其他一些关于python中线程文件复制的SO帖子,它们似乎都说没有速度增益,但考虑到我的硬件,我认为不会出现这种情况。目前我还没有达到IO上限,资源大约占1%(我本地有40个内核和64g RAM)。

这可以通过在Python中使用来并行化

我建议使用以下逻辑来加速100k+的文件复制:

  • 将需要复制的所有100K+文件的名称放入csv文件中,例如:“input.csv”

  • 然后从该csv文件创建块。块的数量应根据计算机中处理器/内核的数量来确定

  • 将这些块传递给单独的线程

  • 每个线程依次读取该块中的文件名,并将其从一个位置复制到另一个位置

  • 下面是python代码片段:

    import sys
    import os
    import multiprocessing
    
    from gevent import monkey
    monkey.patch_all()
    
    from gevent.pool import Pool
    
    def _copyFile(file):
        # over here, you can put your own logic of copying a file from source to destination
    
    def _worker(csv_file, chunk):
        f = open(csv_file)
        f.seek(chunk[0])
        for file in f.read(chunk[1]).splitlines():
            _copyFile(file)
    
    
    def _getChunks(file, size):
        f = open(file)
        while 1:
            start = f.tell()
            f.seek(size, 1)
            s = f.readline()
            yield start, f.tell() - start
            if not s:
                f.close()
                break
    
    if __name__ == "__main__":
        if(len(sys.argv) > 1):
            csv_file_name = sys.argv[1]
        else:
            print "Please provide a csv file as an argument."
            sys.exit()
    
        no_of_procs = multiprocessing.cpu_count() * 4
    
        file_size = os.stat(csv_file_name).st_size
    
        file_size_per_chunk = file_size/no_of_procs
    
        pool = Pool(no_of_procs)
    
        for chunk in _getChunks(csv_file_name, file_size_per_chunk):
            pool.apply_async(_worker, (csv_file_name, chunk))
    
        pool.join()
    
    将文件另存为file_copier.py。 打开终端并运行:

    $ ./file_copier.py input.csv
    
    更新:

    我从来没有让Gevent工作过(第一个答案),因为我无法在没有internet连接的情况下安装模块,而我的工作站上没有internet连接。然而,仅仅使用内置的python线程(我已经学会了如何使用),我就能够将文件复制时间减少8次,我想把它作为一个额外的答案发布给任何感兴趣的人!下面是我的代码,请务必注意,由于您的硬件/网络设置,我的8x拷贝时间很可能因环境而异

    import Queue, threading, os, time
    import shutil
    
    fileQueue = Queue.Queue()
    destPath = 'path/to/cop'
    
    class ThreadedCopy:
        totalFiles = 0
        copyCount = 0
        lock = threading.Lock()
    
        def __init__(self):
            with open("filelist.txt", "r") as txt: #txt with a file per line
                fileList = txt.read().splitlines()
    
            if not os.path.exists(destPath):
                os.mkdir(destPath)
    
            self.totalFiles = len(fileList)
    
            print str(self.totalFiles) + " files to copy."
            self.threadWorkerCopy(fileList)
    
    
        def CopyWorker(self):
            while True:
                fileName = fileQueue.get()
                shutil.copy(fileName, destPath)
                fileQueue.task_done()
                with self.lock:
                    self.copyCount += 1
                    percent = (self.copyCount * 100) / self.totalFiles
                    print str(percent) + " percent copied."
    
        def threadWorkerCopy(self, fileNameList):
            for i in range(16):
                t = threading.Thread(target=self.CopyWorker)
                t.daemon = True
                t.start()
            for fileName in fileNameList:
                fileQueue.put(fileName)
            fileQueue.join()
    
    ThreadedCopy()
    

    在重新实现@Spencer发布的代码时,我遇到了与帖子下面评论中提到的相同的错误(更具体地说:
    OSError:[Errno 24]打开的文件太多了
    )。 我解决了这个问题,不再使用守护线程,而是使用
    concurrent.futures.ThreadPoolExecutor
    。这似乎可以更好地处理要复制的文件的打开和关闭。通过这样做,除了
    threadWorkerCopy(self,filename\u list:list[str])
    方法外,所有代码都保持不变,该方法现在如下所示:

        def threadWorkerCopy(self, filename_list: List[str]):
        """
        This function initializes the workers to enable the multi-threaded process. The workers are handles automatically with
        ThreadPoolExecutor. More infos about multi-threading can be found here: https://realpython.com/intro-to-python-threading/.
        A recurrent problem with the threading here was "OSError: [Errno 24] Too many open files". This was coming from the fact
        that deamon threads were not killed before the end of the script. Therefore, everything opened by them was never closed.
    
        Args:
            filename_list (List[str]): List containing the name of the files to copy.
        """
        with concurrent.futures.ThreadPoolExecutor(max_workers=cores) as executor:
            executor.submit(self.CopyWorker)
    
            for filename in filename_list:
                self.file_queue.put(filename)
            self.file_queue.join()  # program waits for this process to be done.
    

    使用
    线程池如何

    import os
    import glob
    import shutil
    from functools import partial
    from multiprocessing.pool import ThreadPool
    
    DST_DIR = '../path/to/new/dir'
    SRC_DIR = '../path/to/files/to/copy'
    
    # copy_to_mydir will copy any file you give it to DST_DIR
    copy_to_mydir = partial(shutil.copy, dst=DST_DIR))
    
    # list of files we want to copy
    to_copy = glob.glob(os.path.join(SRC_DIR, '*'))
    
    with ThreadPool(4) as p:
      p.map(copy_to_mydir, to_copy)
    
    

    如果您只想将目录树从一个路径复制到另一个路径,下面是我的解决方案,它比以前的解决方案简单一些。它利用了
    multiprocessing.pool.ThreadPool
    ,并为
    shutil.copytree
    使用自定义复制功能:

    import shutil
    from multiprocessing.pool import ThreadPool
    
    
    class MultithreadedCopier:
        def __init__(self, max_threads):
            self.pool = ThreadPool(max_threads)
    
        def copy(self, source, dest):
            self.pool.apply_async(shutil.copy2, args=(source, dest))
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.pool.close()
            self.pool.join()
    
    
    src_dir = "/path/to/src/dir"
    dest_dir = "/path/to/dest/dir"
    
    
    with MultithreadedCopier(max_threads=16) as copier:
        shutil.copytree(src_dir, dest_dir, copy_function=copier.copy)
    

    你看过
    线程。线程
    ?你可以创建多个线程,启动并加入它们,我不确定这是否会有帮助,但这是我唯一能想到的。嘿,胡安,我当然可以深入研究。我想我的问题更多的是,是否值得教我自己如何去做,而最终可能不会更快。换句话说,有没有人有使用线程加速复制时间的经验?嗯,基于此,我认为多处理比线程更好,因为“进程有独立的I/O调度”。你可以尝试类似的方法,它不太复杂,我从未使用过多处理,只是多线程。希望能有帮助。@JohnMee最终成功了!对我来说最合适的地方是16个核。事实上,我看到20岁以后速度下降了。感谢您的详细回复!我得花点时间来处理这件事,并确保我了解一切。在我有机会这么做之前,我忘了提一下,有时候我只有几个文件要处理-所以它会从几个文件到100k+不等。这样做是否会显著降低速度?我想我可以设置一个阈值,如果超过n个文件,那么就多线程处理它。不客气!:)我希望我的解决方案能对你有所帮助。是的,与顺序处理相比,您将看到速度显著降低。我觉得为不同数量的文件设置一个阈值是个好主意。对于代码段,您可以设置n=no\u of_cores*4。此解决方案适用于1个文件。我怎样才能循环通过多个包含路径的文件?我试着循环这段代码,但在某个时候,我会得到一个错误“无法启动新线程”嘿,山姆,你是对的,这里缺少一个前一步。请注意,有一个名为“fileQueue”的队列对象,在运行线程之前,需要用包含源文件和目标文件的元组填充该对象。类似于fileQueue.put(((“path/to/source/file.txt”、“path/to/dest/file.txt”))。查看上的文档(非常简单)。非常感谢斯宾塞抽出时间回复我的评论。@斯宾塞:我得到错误:OSError:[Errno 24]打开的文件太多,你知道为什么吗?16意味着16核吗?