Python将大量文件错误复制到许多打开的文件中

时间:2018-08-02 22:17:41

标签: python

我正在尝试将大量文件从一个目录复制到另一个目录。但是,在尝试通过使用Threading加快处理速度时,出现错误,提示它打开文件过多。目前,文件的测试批次约为700多个,下面是代码。我该如何解决?在我的示例中,我将文件从网络上的一个位置复制到同一网络上的另一个位置,文件范围从1mb到100mb。

def copy_file_to_directory(file, directory):
    '''
    Description:
        Copies the file to the supplied directory if it exists
    '''
    if os.path.isfile(file):
        url = os.path.join(directory, os.path.basename(file))
        try:
            shutil.copyfile(file, url)
            shutil.copystat(file, url)
            return True
        except IOError as e:
            print (e)
            return False

def copy_files_to_directory(files, directory):
    '''
    Directory:
        Copy a list of files to directory, overwriting existing files
    '''
    if not os.path.isdir(directory):
        os.makedirs(directory)

    if not os.path.isdir(directory):
        return False

    workers = []   
    for x in files:
        if os.path.isfile(x):
            worker = threading.Thread(target=copy_file_to_directory, args=(x,directory))
            workers.append(worker.start())

    # wait until they are all done processing
    for x in workers:
        x.join()

    return True

 files = [] # list of files
 copy_files_to_directory(files, 'C:/Users/John')

2 个答案:

答案 0 :(得分:3)

几乎可以肯定,您不想让每个文件产生一个线程。在一定程度上,线程化可以为您带来好处(并且您不只是饱和磁盘I / O带宽),您可能应该仅使用具有固定线程数的线程池(例如,并发.futures.ThreadPoolExecutor)。这将限制一次打开的文件数。实际上,这种情况在Python文档中作为示例给出:series of articles

使它适合您的使用:

with ThreadPoolExecutor(max_workers=4) as e:
    for x in files:
        if os.path.isfile(x):
            e.submit(copy_file_to_directory, x, directory)

答案 1 :(得分:1)

我对线程池(4个线程池和8个线程池),直接shutil和文件的OS副本(即,不在Python中)进行了一些计时。

目标设备是以下设备之一:

  1. 本地旋转硬盘;
  2. 具有Thunderbolt 3接口的快速外部SSD;
  3. 一个在安装设备上带有SSD并具有1000 base T接口的SMB网络安装点。

源设备是非常快的Mac内部SSD,能够进行8K视频编辑,比任何目标设备都快得多。

首先创建100个介于1 MB和100MB之间的随机数据文件:

#!/bin/bash
cd /tmp/test/src   # a high bandwidth source SSD

for fn in {1..100}.tgt 
do 
   sz=$(( (1 + RANDOM % 100)*1000*1000 ))
   printf "creating %s with %s MB\n" "$fn" $((sz/(1000*1000) ))
   head -c "$sz" </dev/urandom >"$fn"
done

现在计时码:

import shutil
import os
import pathlib
import concurrent.futures
import random 

def copy_file_to_directory(file, directory):
    '''
    Description:
        Copies the file to the supplied directory if it exists
    '''
    if os.path.isfile(file):
        url = os.path.join(directory, os.path.basename(file))
        try:
            shutil.copyfile(file, url)
            shutil.copystat(file, url)
            return True
        except IOError as e:
            print (e)
            return False

def f1(files, directory):
    '''
    Directory:
        Copy a list of files to directory, overwriting existing files
    '''

    if not os.path.isdir(directory):
        os.makedirs(directory)

    if not os.path.isdir(directory):
        return False

    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e:
        for x in files:
            if os.path.isfile(x):
                e.submit(copy_file_to_directory, x, directory)
    return True     

def f2(files, directory):
    '''
    Directory:
        Copy a list of files to directory, overwriting existing files
    '''

    if not os.path.isdir(directory):
        os.makedirs(directory)

    if not os.path.isdir(directory):
        return False

    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as e:
        for x in files:
            if os.path.isfile(x):
                e.submit(copy_file_to_directory, x, directory)
    return True     

def f3(files, p):
    '''
    Serial file copy using copy_file_to_directory one file at a time
    '''
    for f in files:
        if os.path.isfile(f):
            copy_file_to_directory(f, p)

if __name__=='__main__':
    import timeit
    src='/tmp/test/src'
    cnt=0
    sz=0
    files=[]
    for fn in pathlib.Path(src).glob('*.tgt'):
        sz+=pathlib.Path(fn).stat().st_size
        cnt+=1
        files.append(fn)
    print('{:,.2f} MB in {} files'.format(sz/(1000**2),cnt))    

    for case, tgt in (('Local spinning drive','/Volumes/LaCie 2TB Slim TB/test'),('local SSD','/Volumes/SSD TM/test'),('smb net drive','/Volumes/andrew/tgt-DELETE')):  
        print("Case {}=> {}".format(case,tgt))
        for f in (f1,f2,f3):
            print("   {:^10s}{:.4f} secs".format(f.__name__, timeit.timeit("f(files, tgt)", setup="from __main__ import f, files, tgt", number=1)))  

结果是:

4,740.00 MB in 100 files
Case Local spinning drive=> /Volumes/LaCie 2TB Slim TB/test
       f1    56.7113 secs
       f2    71.2465 secs
       f3    46.2672 secs
Case local SSD=> /Volumes/SSD TM/test
       f1    9.7915 secs
       f2    10.2333 secs
       f3    10.6059 secs
Case smb net drive=> /Volumes/andrew/tgt-DELETE
       f1    41.6251 secs
       f2    40.9873 secs
       f3    51.3326 secs

并与原始unix复制时间进行比较:

$ time cp /tmp/test/src/*.* "/Volumes/LaCie 2TB Slim TB/test"
real    0m41.127s

$ time cp /tmp/test/src/*.* "/Volumes/SSD TM/test"
real    0m9.766s

$ time cp /tmp/test/src/*.* "/Volumes/andrew/tgt-DELETE"
real    0m49.993s

我怀疑,时间(至少对于MY测试)大致相同,因为限制速度是底层I / O带宽。网络设备的线程池具有一些优点,但在机械驱动器上却有很大的缺点。

这些结果仅用于从文件的一个同质位置复制到另一同质位置,而不处理单个文件。如果这些步骤涉及每个文件的某些CPU密集型功能,或者单个文件的目标涉及不同的I / O路径(即,一个文件到SSD,并且根据某种情况下一个文件到网络等),则可能赞成使用并发方法。