如何制作多线程复制文件

时间:2014-06-05 17:22:32

标签: multithreading file delphi synchronization copy

我想将多个文件复制到一个,但是使用multiThread,假设文件A是不同线程复制数据的文件,在这种情况下,每个线程都要复制文件A中的一个文件,使用以下过程: / p>

procedure ConcatenateFiles(const InFileNames: array of string;
const OutFileName: string);
var
i: Integer;
InStream, OutStream: TFileStream;
begin
OutStream := TFileStream.Create(OutFileName, fmCreate);
try
 for i := 0 to high(InFileNames) do
 begin
  InStream := TFileStream.Create(InFileNames[i], fmOpenRead);
  try
    OutStream.CopyFrom(InStream, InStream.Size);
  finally
    InStream.Free;
  end;
 end;
finally
 OutStream.Free;
end;

端;

首先,在这种情况下是否可以实现多线程复制文件,因为OutFileName是一个全局变量,两个线程不能同时使用它,这是我得到的错误, 如果这是可能的,我怎么能同步线程以避免瞬间使用多个进程的OutFileName? 制作多线程复制文件真的很有效,我说的是复制文件的速度。 谢谢你的回复

3 个答案:

答案 0 :(得分:4)

使用多个线程复制文件是完全可能的。您通常会使用单个生产者线程和多个使用者来完成工作。在你的情况下,你是连接。因此,您需要计算每个源文件的起点和终点,然后让线程在预先计算的位置写入目标文件的单独部分。当然可能。

然而,这不是一个好主意。当作业受CPU限制时,多线程可以很好地工作。文件复制是磁盘绑定的,没有多少额外的线程可以提供帮助。实际上,您最终可能会使性能变差,因为多个线程将在争夺共享磁盘资源的同时以其他方式进入。

答案 1 :(得分:2)

如果要将多个输入文件并行连接到单个目标文件中,可以这样做:

  1. 预分配目标文件。创建文件,寻找目标最终连接的文件大小,并设置EOF以在文件系统上分配文件。使用TFileStream,只需将TFileStream.Size属性设置为预期大小即可实现。否则,直接使用Win32 API,您必须使用CreateFile()SetFilePointer()SetEndOfFile()

  2. 将目标文件划分为逻辑部分,每个部分在文件中都有一个起始和结束偏移量,并根据需要将这些部分分配给您的线程。让每个线程为同一目标文件打开自己的本地句柄。这将允许每个线程独立地搜索和写入。确保每个线程都不会离开其指定的部分,这样就不会破坏另一个线程的写入数据。

  3. 例如:

    type
      TFileInfo = record
        InFileName: String;
        OutFileName: String;
        OutFileStart: Int64;
        OutFileSize: Int64;
      end;
    
      TCopyThread = class(TThread)
      protected
       FFileInfo: TFileInfo;
       procedure Execute;
      public
        constructor Create(const AFileInfo: TFileInfo);
      end;
    
    constructor TCopyThread.Create(const AFileInfo: TFileInfo);
    begin
      inherited Create(False);
      FFileInfo := AFileInfo;
     end;
    
    procedure TCopyThread.Execute;
    var
      InStream: TFileStream;
      OutStream: TFileStream;
    begin
      InStream := TFileStream.Create(FFileInfo.InFileName, fmOpenRead or fmShareDenyWrite);
      try
        OutStream := TFileStream.Create(FFileInfo.OutFileName, fmOpenWrite or fmShareDenyNone);
        try
          OutStream.Position := FFileInfo.OutFileStart;
          OutStream.CopyFrom(InStream, FFileInfo.OutFileSize);
        finally
          OutStream.Free;
        end;
      finally
        InStream.Free;
      end;
    end;
    
    procedure ConcatenateFiles(const InFileNames: array of string; const OutFileName: string);
    var
      i: Integer;
      OutStream: TFileStream;
      FileInfo: array of TFileInfo;
      TotalSize: Int64;
      sr: TSearchRec;
      Threads: array of TCopyThread;
      ThreadHandles: array of THandle;
      NumThreads: Integer;      
    begin
      SetLength(FileInfo, Length(InFileNames));
      NumThreads := 0;
      TotalSize := 0;
    
      for i := 0 to High(InFileNames) do
      begin
        if FindFirst(InFileNames[i], faAnyFile, sr) <> 0 then
          raise Exception.CreateFmt('Cannot retrieve size of file: %s', [InFileNames[i]]);
    
        if sr.Size > 0 then
        begin
          FileInfo[NumThreads].InFileName := InFileNames[i];
          FileInfo[NumThreads].OutFileName := OutFileName;
          FileInfo[NumThreads].OutFileStart := TotalSize;
          FileInfo[NumThreads].OutFileSize := sr.Size;
          Inc(NumThreads);
          Inc(TotalSize, sr.Size);
        end;
    
        FindClose(sr); 
      end;
    
      OutStream := TFileStream.Create(OutFileName, fmCreate);
      try
        OutStream.Size := TotalSize;
      finally
        OutStream.Free;
      end;
    
      SetLength(Threads, NumThreads);
      SetLength(ThreadHandles, NumThreads);
    
      for i := 0 to NumThreads-1 do
      begin
        Threads[i] := TCopyThread.Create(FileInfo[i]);
        ThreadHandles[i] := Threads[i].Handle;
      end;
    
      i := 0;
      while i < NumThreads do
      begin
        WaitForMultipleObjects(Min(NumThreads-i, MAXIMUM_WAIT_OBJECTS), ThreadHandles[i], TRUE, INFINITE);
        Inc(i, MAXIMUM_WAIT_OBJECTS);
      end;
    
      for i := 0 to NumThreads-1 do
      begin
        Threads[i].Terminate;
        Threads[i].WaitFor;
        Threads[i].Free;
      end;
    end;
    

答案 2 :(得分:0)

如前所述,已经从多个线程写入同一个文件并不是一个好主意。

如果您尝试以多线程共享相同文件句柄的方式执行此操作,则最终会遇到一个大问题,即确保一个线程使用Seek命令移动文件位置,而另一个线程尝试写入一些数据。

如果您尝试以每个线程创建自己的文件句柄的方式执行此操作,那么您最终会遇到以下问题:操作系统通常不会因为具有写入功能而具有多个文件句柄,因为这可能是灾难的接收者(数据coruption)。

现在,即使你以某种方式设法使这个工作,以便每个胎面都写在文件的自己的部分,并且他们没有互相搞乱,你仍然会因硬盘限制而丢失一些性能(硬盘驱动器头需要重新定位到核心位置 - 大量的来回运动)。

嘿但是你可以使用多个线程在你的硬盘上写入最终文件之前准备你的内存。这很容易实现,因为内存访问速度非常快,以至于您几乎不会因来回跳跃而失去任何优势。唯一的问题是,如果你要汇总几个较大的文件,你可能会很快耗尽内存。

编辑:顺便说一句,如果你感兴趣我可以分享我几年前制作的两个线程双缓冲文件复制示例的代码示例。请注意,它不提供任何数据验证功能,因为它只是为了测试理论而写的,或者说我打破了一个理论,即只能用Delphi复制文件(没有从Windows获取文件复制API)。在同一个硬盘上进行文件复制时,它比Windows例程中的内容慢一些,但是当从一个硬盘复制到另一个硬盘时,它的速度与在例程中构建的窗口速度相同。