程序中有多个线程并发执行,每个线程中都会记录日志文件(以天为单位生成文件),写文件非常频繁。20分钟内有3000多个线程。
这样导致在写文件的过程中发生I/o error 32 错误。有什么好的解决方案。

解决方案 »

  1.   

    我这里有一个是使用前一种方式做的DLL,效率还不错。但是不支持多进程同时使用。只能单进程控制文件,如果需要支持多进程,就需要改为COM,并且效率会明显下降。对于后一种比较常见的就是一些第三方的日志记录器。
      

  2.   


    (******************************************************************************)
    (*  模 块 名: HSLogger4D.Pas                                                 *)
    (*  别    名: 多任务线程安全日志接口-进程独立版                               *)
    (*  作    者: Unsigned(僵哥)                                 *)
    (*  说    明: 基于线程安全和文件缓存的单例多线程同步日志记录接口,本接口由一个 *)
    (*            Delphi版本的动态链接库来提供相应的功能,采用Delphi的 String 类型 *)
    (*            传递参数,使用时请注意调用方式与Delphi一致,不建议使用于 Delphi   *)
    (*            以外的语言环境,由此所引发的后果,作者不承担任何责任             *)
    (*  附带文件: HSLogger4D.DLL                                                  *)
    (*  版 本 号: 1.1                                                             *)
    (*  创 建 于: 2007-07                                                         *)
    (*  备    注: 本接口属进程独立版不处理多进程间同步                            *)
    (*                                                                            *)
    (*  修改记录: 2007-07-13 创建最初版本                                         *)
    (*            2007-07-14 修正多线程内存管理冲突                               *)
    (******************************************************************************)unit HSLogger4D;interface
    uses
      Windows;
    (*********************************)
    (* 功 能:记录日志                *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:日志内容,自行控制换行符 *)
    (* 参数3:记录日志后是否关闭文件  *)
    (*       (不会释放其它资源)      *)
    (* 返回值:0=成功,非0=LastError   *)
    (*********************************)
    function WriteLogThreadsafe(
                                FileName  : String;
                                LogString : String;
                                CloseFileAfterWrote : BOOL
                                ) : DWORD;overload;function WriteLogThreadsafe(
                                FileName  : String;
                                LogString : String
                                ) : DWORD;overload;
    (*********************************)
    (* 功 能:关闭日志文件            *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:记录日志文件关闭后是否  *)
    (*       释放其它相关资源        *)
    (*********************************)
    procedure CloseFile(
                        FileName : String;
                        FreeAfterClose  : BOOL  = true
                        );(*********************************)
    (* 功 能:关闭所有日志文件        *)
    (* 参数1:记录日志文件关闭后是否  *)
    (*       释放其它相关资源        *)
    (*********************************)
    procedure CloseAllFile(
                            FreeAfterClose  : BOOL  = false
                            );(*********************************)
    (* 功 能:强制缓存写入文件        *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:写入文件后是否关闭文件  *)
    (*       (不会释放其它资源)      *)
    (* 返回值:0=失败,非0=成功        *)
    (*********************************)
    function FlushFileToDisk(
                              FileName  : String;
                              CloseFileAfterFlush : BOOL    = true
                              ) : BOOL;(*********************************)
    (* 功 能:强制缓存写入文件        *)
    (* 参数1:写入文件后是否关闭文件  *)
    (*       (不会释放其它资源)      *)
    (*********************************)
    procedure FlushAllFileToDisk(
                                  CloseFileAfterFlush : BOOL  = false
                                  );var
      CloseFileAfterWrote:BOOL=false;implementation(******************************************************************************)
    (*                                                                            *)
    (*                            <<日志接口定义>>                                *)
    (*                         注:请保持与源接口一致                              *)
    (******************************************************************************)
    type
      (*Logger Interface*)
      ILogger=interface
        ['{E43E419D-26AD-48e6-8097-19622CC2043E}']
        function WriteLog(
                          FileName  : String;
                          LogString : String;
                          CloseFileAfterWrote : BOOL  = true
                          ) : DWORD;    procedure CloseFile(
                          FileName  : String;
                          FreeAfterClose  : BOOL  = true
                          );    procedure CloseAllFile(
                          FreeAfterClose  : BOOL  = true
                          );    function  FlushFileToDisk(
                          FileName  : String;
                          CloseFileAfterFlush : BOOL  = true
                          ) : BOOL;    procedure FlushAllFileToDisk(
                          CloseFileAfterFlush : BOOL  = true
                          );
      end;  (*Logger Import statement*)
      function HSLoggerObject: ILogger; stdcall;
                                        external  'HSLogger4D.dll'
                                        name      'HSLoggerObject';(*Write Log-String to file*)
    (*********************************)
    (* 功 能:记录日志                *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:日志内容,自行控制换行符 *)
    (* 参数3:记录日志后是否关闭文件  *)
    (*       (不会释放其它资源)      *)
    (*********************************)
    function WriteLogThreadsafe(
                                FileName            : String;
                                LogString           : String;
                                CloseFileAfterWrote : BOOL
                                ) : DWORD;
    begin
      Result:=HSLoggerObject.WriteLog(
                                      FileName,
                                      LogString,
                                      CloseFileAfterWrote
                                      );
    end;function WriteLogThreadsafe(
                                FileName            : String;
                                LogString           : String
                                ) : DWORD;
    begin
      Result:=WriteLogThreadsafe(
                                  FileName,
                                  LogString,
                                  CloseFileAfterWrote
                                );
    end;(*Close one Logfile,and free logfile-Object if FreeAfterClose was set to TRUE*)
    (*********************************)
    (* 功 能:关闭日志文件            *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:记录日志文件关闭后是否  *)
    (*********************************)
    procedure CloseFile(
                        FileName  : String;
                        FreeAfterClose  : BOOL);
    begin
      HSLoggerObject.CloseFile(
                                FileName,
                                FreeAfterClose
                                );
    end;(*Close All Logfile,and free logfile-Object if FreeAfterClose was set to TRUE*)
    (*********************************)
    (* 功 能:关闭所有日志文件        *)
    (* 参数1:记录日志文件关闭后是否  *)
    (*********************************)
    procedure CloseAllFile( FreeAfterClose  : BOOL  = false );
    begin
      HSLoggerObject.CloseAllFile(FreeAfterClose);
    end;(*********************************)
    (* 功 能:强制缓存写入文件            *)
    (* 参数1:日志文件名,支持相对路径 *)
    (* 参数2:写入文件后是否关闭文件  *)(* 返回值:0=失败,非0=成功        *)
    (*********************************)
    function FlushFileToDisk( FileName  : String;
                              CloseFileAfterFlush : BOOL
                              ) :  BOOL;
    begin
      Result:=HSLoggerObject.FlushFileToDisk(
                                              FileName,
                                              CloseFileAfterFlush
                                              );
    end;(*********************************)
    (* 功 能:强制缓存写入文件        *)
    (* 参数1:写入文件后是否关闭文件  *)
    (*********************************)
    procedure FlushAllFileToDisk( CloseFileAfterFlush : BOOL  );
    begin
      HSLoggerObject.FlushAllFileToDisk(CloseFileAfterFlush);
    end;
    end.
      

  3.   

    刚上传上去,希望各楼主有用。不过请注意的话,文件编译有公司的版权信息,建议谨慎使用,特别是在部分比较严谨的用户那里,可能会遭到用户的排斥。
    http://download.csdn.net/source/332395
      

  4.   

    谢谢unsigned,感觉这个很复杂的样子,不知道有没有最简单的解决办法
      

  5.   

    apache 的开源 Log4d 试试
      

  6.   

    调用就是将上面我贴出来的存为一个Unit(RAR压缩包当中有),然后就调用WriteLogThreadsafe([文件名],[日志内容],[是否需要关闭文件])一直打开可以提升重复写文件的效率,而重复性比较小的就可以关闭掉。而且这个是否关闭文件也是可以通过设置全局CloseFileAfterWrote来控制的,这样就有一个overload版的WriteLogThreadsafe.
      

  7.   

    如果某个文件不需要用了,可以调用CloseFile来关闭,后面的参数还可以指定是否释放相关的资源。CloseAllFile可以关闭所有当前打开的文件,参数FreeAfterClose控制是否释放资源。这个同时支持相对路径(以应用程序的路径为基准点,没有测试过网络路径)。
      

  8.   

    日志文件类及实现
      (*TFileLoggerObject*)
      TFileLoggerObject=class(TObject)
        private
          FFileName:String;                               //文件名
          FFileHandle:THandle;                            //文件句柄
          {$ifdef _MT}
          FFileLock:TRTLCriticalSection;                  //文件操作开关
          {$endif}
        protected
          procedure InternalCloseFile;                    //关闭文件
          function WriteLog(LogMsg:String):Integer;       //日志信息入库 **写入文件**    protected
          function TryLockFile:BOOL;                      //文件开关尝试入口
          procedure LockFile;                             //文件开关入口
          procedure UnlockFile;                           //文件开关出口    public
          (*构造函数*)
          constructor Create( LogFileName:String         //文件名,必须
                              );
          (*析构函数*)
          Destructor Destroy;override;    public (*<<属性>>*)
          (* <<只读>> 文件名                 *)
          property FileName:String read FFileName;      (* <<只读>> 文件句柄               *)
          property FileHandle:THandle read FFileHandle;    public      (* 无队列日志写入,通过对比较重要的信息采用此接口 *)
          procedure PushLog(LogMsg:String;var ErrorCode:DWORD;bCloseFile:BOOL=false);      (* 关闭日志文件 *)
          procedure CloseFile;      (* 强制文件缓存写入物理文件 *)
          function FlushFileToDisk(CloseFileAfterFlush:BOOL=true):BOOL;  end;
    (******************************************************************************)
    (*                             <<日志文件类实现>>                             *)
    (******************************************************************************)
    (*TFileLoggerObject*)procedure TFileLoggerObject.PushLog(LogMsg: string; var ErrorCode: DWORD;bCloseFile:BOOL);
    begin
      LockFile;
      try
        ErrorCode:=WriteLog(LogMsg);
        if bCloseFile  then
          begin
            InternalCloseFile;
          end;
      finally
        UnlockFile;
      end;
    end;procedure TFileLoggerObject.CloseFile;
    begin
      LockFile;
      try
        InternalCloseFile;
      finally
        UnlockFile;
      end;
    end;Function TFileLoggerObject.FlushFileToDisk(CloseFileAfterFlush: BOOL):BOOL;
    begin
      Result:=false;
      LockFile;
      try
        if FFileHandle=INVALID_HANDLE_VALUE then
          begin
            Exit;
          end;
        Result:=FlushFileBuffers(FFileHandle);
        if CloseFileAfterFlush then
          InternalCloseFile;
        
      finally
        UnlockFile;
      end;
    end;procedure TFileLoggerObject.InternalCloseFile;
    begin
      if (FFileHandle<>INVALID_HANDLE_VALUE) then
        begin
          CloseHandle(FFileHandle);
          FFileHandle:=INVALID_HANDLE_VALUE;
        end;
    end;function TFileLoggerObject.WriteLog(LogMsg: string):Integer;
    var
      WroteSize:DWORD;
      BytesToWrite:DWORD;
      Buffer:Pointer;
    begin
      BytesToWrite:=Length(LogMsg);
      Result:=0;
      if BytesToWrite<=0 then
        begin
          Exit;
        end;
      
      if self.FFileHandle=INVALID_HANDLE_VALUE then
        begin
          try
            if Not CheckDir(ExtractFilePath(FileName)) then
              begin
                Result := GetLastError;
                Exit;
              end;
            FFileHandle:=CreateFile(@FileName[1],
                                    GENERIC_READ or GENERIC_WRITE,
                                    FILE_SHARE_READ,
                                    nil,
                                    OPEN_ALWAYS,//CREATE_NEW
                                    FILE_ATTRIBUTE_NORMAL,
                                    0);
            if FFileHandle=INVALID_HANDLE_VALUE then
              begin
                Result:=GetLastError;
                Exit;
              end;
            if GetLastError=ERROR_ALREADY_EXISTS then
              begin
                if SetFilePointer(FFileHandle,0,nil,FILE_END)=INVALID_SET_FILE_POINTER then
                  begin
                    Result:=GetLastError;
                  end
                else
                  SetLastError(0);
              end;
          except
            Result:=GetLastError;
            Exit;
          end;
        end;
        Buffer:=Pointer(LogMsg);
        while BytesToWrite>0 do
          begin
            WroteSize:=0;
            if Not WriteFile(FFileHandle,Buffer^,BytesToWrite,WroteSize,nil) then
              begin
                Result:=GetLastError;
                Exit;
              end;
            Buffer:=Pointer(DWORD(Buffer)+WroteSize);
            Dec(BytesToWrite,WroteSize);
          end;end;procedure TFileLoggerObject.LockFile;
    begin
      {$ifdef _MT}
      EnterCriticalSection(FFileLock);
      {$endif}
    end;procedure TFileLoggerObject.UnlockFile;
    begin
      {$ifdef _MT}
      LeaveCriticalSection(FFileLock);
      {$endif}
    end;function TFileLoggerObject.TryLockFile:BOOL;
    begin
      {$ifdef _MT}
      Result:=TryEnterCriticalSection(FFileLock);
      {$else}
      Result:=True;
      {$endif}
    end;constructor TFileLoggerObject.Create(LogFileName: string);
    begin
      FFileName:=LogFileName;
      FFileHandle:=INVALID_HANDLE_VALUE;
      {$ifdef _MT}
      InitializeCriticalSection(FFileLock);
      {$endif}
    end;destructor TFileLoggerObject.Destroy;
    begin
      LockFile;
      try
        if FFileHandle<>INVALID_HANDLE_VALUE then
          CloseHandle(FFileHandle);
      finally
        UnlockFile;
        {$ifdef _MT}
        DeleteCriticalSection(FFileLock);
        {$endif}
      end;
      Inherited;
    end;
      

  9.   

    其中这当中有一个问题是      ...
          except
            Result:=GetLastError;
            Exit;
          end;
          ...这里的Result永远都为0.如果实际上在这个时候,单纯造返回错误号,已经没太多的意义,这应该是一个未知的错误,建议性改用-1.
      

  10.   

    CheckDir
    SetFilePointer(FFileHandle,0,nil,FILE_END)=INVALID_SET_FILE_POINTER 
    这个是要引用Delphi的哪个系统单元啊。
      

  11.   

    Windows.pas
    并发写文件如此并发,还是用Pooling技术吧
    自己实现线程池来处理
    或考虑减少并发
    重要的是俺认为你的设计存在结构错误,如此多线程的文件操作,最好避免
      

  12.   

    CheckDir 
    SetFilePointer(FFileHandle,0,nil,FILE_END)=INVALID_SET_FILE_POINTER   
    这个是要引用Delphi的哪个系统单元啊。 
    ==================
    SetFilePointer是一个系统API,引用Windows单元就可以。
    CheckDir是自己写的一个函数
    function CheckDir(const Dir:String):Boolean;
    var
      DirList:TStringList;
      SubDir:String;
      I: Integer;
    begin
      Result := True;
      SubDir:=Dir;
      if DirectoryExists(SubDir) then
        Exit;  DirList:=TStringList.Create;
      try
        repeat
          while SubDir[Length(SubDir)] ='\' do SubDir := Copy(SubDir,1,Length(SubDir)-1);
          if Length(SubDir)=0 then
            break;
          DirList.Add(SubDir);
          SubDir := ExtractFilePath(SubDir);
          if DirectoryExists(SubDir) then break;
        until true;
        for I := DirList.Count downto  1 do
          begin
            SubDir := DirList.Strings[i-1];
            if Not CreateDirectory(@SubDir[1],nil) then
              begin
                Result := false;
                Exit;
              end;
          end;
      finally
        DirList.Free;
      end;
    end;其实楼主说的仅仅只是记录日志,不是比较严谨的文件操作,没有太多必要进行内存池操作,当然如果实现了完成端口去操作,反倒比较推荐去使用。另外楼主说的20分钟有三千个线程操作文件,这个得看具体的情况。我所采用的是一种单例模式管理。相对操作同一个文件来讲,多个线程确实存在着排队和撞车事件。使用队列虽然可以让需要记日志的线程独立出来,但是却增大了内存的开销。并且对同一文件的操作同一时间也只能是一个线程进行操作。因为谁也无法预期到一次可能会存多少内容,或者说一次写文件操作并不一定能完成,如果就有可能导致文件操作过程当中的数据混乱。其实在最初的设想当中,我也想过实现日志队列,但是这个就需要库当中进行线程管理。而线程管理,特别是作为一个类似插件来为别人做一个独立的线程管理,这是不推荐的,因为任何可能的问题都会出现,并且双方都不可以预知,从而加入了线程会远远增大复杂度,而我的目标仅仅只是一条条完整地记录日志数据,在高并发服务当中,记录日志也仅仅只是其中的一小小部分。当然,日志也有多种多样的类型,还有一些日志是非常重要的,甚至还需要防篡改,和复杂查询(可能需要入库)等等,那时候日志就是一整个项目当中致关重要的工作了。个人觉得,这应该不算是这贴子当中讨论的重点,或者至少楼主并没有给出更加明确的信息提示需要如此去探讨,毕竟那牵涉的内容太宽了。
      

  13.   

    谢谢unsigned,受益匪浅!!!