zoukankan      html  css  js  c++  java
  • 高吞吐量的一个日志函数类_用于IOCP (Delphi)

     

          在开发服务器端程序的时候,日志是必须的一个功能。由于服务器端的要频繁的把数据写入日志,开始的时候用了一个很简单日志函数

    就是直接把日志字符写入文件中。然后关闭连接。一直也应用良好。但做压力测试的时候,因为要每个连接的数据都要写入日志,发现运行的一段时间后,频繁掉线,CPU占用率,居高不下,优化了可以想到的很多地方,有一定的效果,仔细观察发现,硬盘灯狂闪不止,说明硬盘I/0操作过于紧张。但测试的时候,基本是不读写硬盘的,恍然发现,是日志函数影响到整个系统的性能。每一个日志数据的时候,就要打开文件,写入文件,关闭文件。哈,这些都是相对昂贵的I/0操作。优化的方法很简单,缓存数据,定期的批量写入磁盘。基于此设计思路就开发了一个新的日志类。以空间换取时间。 

         内部采用双缓冲算法,写入信息的时候,是直接写入到 内存中,然后线程根据一定的时间间隔,将内存中的数据写到磁盘文件中,里面开辟了两块缓冲内存队列,采用了生产者===》消费者模式,WriteLog 是写入日志数据,算是数据的生产者,TFileStream对象,将内存中的数据写入磁盘是消费者角色,由于采用了双缓冲方式,减少了生产与消费间的干扰. 提高了性能,减少日志的写入时间。也勉强算是个双缓冲队列的实际应用.

       此日志类是基于线程实现的。为了方便使用。内部采用了锁定机制。是线程安全的类。当数据量比较大或者为了便于日志文件的管理

    我们会把数据按一定规则生成不同的日志文件名,最常见的就是按日期作日志文件的名称。例如 20110702.log, 20110701.log 等

    此日志类中考虑到此情况,可以随时更改日志文件名。

      property FileName:string read getLogFileName write setLogFileName; 要修改日志文件名,直接赋值即可。也是线程安全的。

    最后要说明下,此日志类的设计思路也可以用于其它方面。缓冲,空间换时间是软件设计中常用的方法。

    在后来的 IOCP 模式下开发的服务器程序(ECHO 测试 12,000连接 OK),应用了此日志类。效果良好


     

    //实现的代码

    unit uSfLog;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes;
    
    type
      TsfLog=class(TThread)
      private
         FLF:string;//#13#10;
         FS:TFileStream;
         FCurFileName:string;
         FFileName:string;
         FBegCount:DWord;
         FBuffA,FBuffB:TMemoryStream;
         FCS:TRTLCriticalSection;
         FCS_FileName:TRTLCriticalSection;
         FLogBuff:TMemoryStream;
         procedure WriteToFile();
         function getLogFileName: string;
         procedure setLogFileName(const Value: string);
       protected
          procedure Execute();override;
       public
          constructor Create(LogFileName:string);
          destructor  Destroy();override;
          procedure WriteLog(const InBuff:Pointer;InSize:Integer);overload;
          procedure WriteLog(const Msg:string);overload;
      public
          property FileName:string read getLogFileName write setLogFileName;
      end;
    
    
    implementation
    
    { TsfLog }
    
    constructor TsfLog.Create(LogFileName:string);
    begin
       if Trim(LogFileName) = '' then
           raise exception.Create('Log FileName not ""');
    
       inherited Create(TRUE);
       //\\
       InitializeCriticalSection(FCS);  //初始化
       InitializeCriticalSection(FCS_FileName);//日志文件名
    
      //队列缓冲区A,B运行的时候,交替使用
    
       Self.FBuffA := TMemoryStream.Create();
       Self.FBuffA.Size := 1024 * 1024; //初始值可以根据需要自行调整
       Self.FBuffB := TMemoryStream.Create();
       Self.FBuffB.Size := 1024 * 1024; //初始值可以根据需要自行调整
       Self.FLogBuff := Self.FBuffA;
    
      
    
       if FileExists(LogfileName) then
       begin
          FS := TFileStream.Create(LogFileName,fmOpenWrite or fmShareDenyWrite);
          FS.Position := FS.Size; //如果文件已经存在,数据进行追加
      end
      else
        FS := TFileStream.Create(LogFileName,fmCreate or fmShareDenyWrite);
    
       FCurFileName := LogFileName;
       FFileName    := LogFileName;
    
       FLF  := #13#10;
    
       //启动执行
       Self.Resume();
      //\\
    end;
    
    destructor TsfLog.Destroy;
    begin
       FBuffA.Free();
       FBuffB.Free();
       FS.Free();
       inherited;
    end;
    
    procedure TsfLog.Execute();
    begin
       FBegCount := GetTickCount();
       while(not Self.Terminated) do
       begin
           //2000ms 可以根据自己的需要调整,数据写入磁盘的间隔
          if (GetTickCount() - FBegCount) >= 2000 then
         begin
            WriteToFile();
            FBegCount := GetTickCount();
        end
        else
           Sleep(200);
       end;
        WriteToFile();
    end;
    
    function TsfLog.getLogFileName: string;
    begin
       EnterCriticalSection(FCS_FileName);
       try
          Result := FCurFileName;
        finally
          LeaveCriticalSection(FCS_FileName);
        end;
    end;
    
    procedure TsfLog.setLogFileName(const Value: string);
    begin
      EnterCriticalSection(FCS_FileName);
      try
        FFileName := Value;
      finally
        LeaveCriticalSection(FCS_FileName);
      end;
    end;
    
    procedure TsfLog.WriteLog(const Msg: string);
    begin
      WriteLog(Pointer(Msg),Length(Msg));
    end;
    
    procedure TsfLog.WriteLog(const InBuff: Pointer; InSize: Integer);
    var
        TmpStr:string;
    begin
        TmpStr := FormatDateTime('YYYY-MM-DD hh:mm:ss zzz ',Now());
        EnterCriticalSection(FCS);
        try
          FLogBuff.Write(TmpStr[1],Length(TmpStr));
          FLogBuff.Write(InBuff^,InSize);
          FLogBuff.Write(FLF[1],2);
        finally
          LeaveCriticalSection(FCS);
      end;
    end;
    
    procedure TsfLog.WriteToFile;
    var
        MS:TMemoryStream;
        IsLogFileNameChanged:Boolean;
    begin
        EnterCriticalSection(FCS);
        //交换缓冲区
        try
          MS := nil;
          if FLogBuff.Position > 0 then
         begin
             MS := FLogBuff;
              if FLogBuff = FBuffA then FLogBuff := FBuffB
              else
                 FLogBuff := FBuffA;
             FLogBuff.Position := 0;
         end;
      finally
         LeaveCriticalSection(FCS);
      end;
      //\\
        if MS = nil then
        Exit;
    
       //写入文件
       try
          FS.Write(MS.Memory^,MS.Position);
        finally
           MS.Position := 0;
        end;
    
         //检测文件名称是否变化
        EnterCriticalSection(FCS_FileName);
        try
           IsLogFileNameChanged := (FCurFileName <> FFileName);
        finally
           LeaveCriticalSection(FCS_FileName);
        end;
    
         //日志文件名称修改了
         if IsLogFileNameChanged then
        begin
            FCurFileName :=  FFileName;
            FS.Free();
           if FileExists(FFileName) then
          begin
             FS := TFileStream.Create(FFileName,fmOpenWrite or fmShareDenyWrite);
             FS.Position := FS.Size;
         end
         else
            FS := TFileStream.Create(FFileName,fmCreate or fmShareDenyWrite);
        end;
    
    end;
    end.
    
     
    
     
    
     
    
     
    
    //日志类函数的测试代码
    
    //主要测试三个功能
    
    1)日志的写入速度是否足够快
    
    2)日志类在多线程情况下,能稳定运行吗?
    
    3)运行中,更换输出日志的文件名称
    
     
    
    为了产生大量的数据及多线程下的稳定性,测试中产生了 120个线程,同时写日志函数。
    
    同时用一个定时器,定期的修改输出日志的文件名。写入日志的信息是随机产生的GUID字符串。
    
     
    
     
    
    unit uMain;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs,uSfLog, StdCtrls, ExtCtrls,ActiveX;
    
    type
      TfrmMain = class(TForm)
        Button1: TButton;
        Edit1: TEdit;
        Timer1: TTimer;
        procedure FormCreate(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
        procedure Timer1Timer(Sender: TObject);
      private
        { Private declarations }
        FList:TList;
        LogObj:TsfLog;
      public
        { Public declarations }
      end;
    
      TsfLogTest=class(TThread)
      protected
        procedure Execute();override;
      end;
    
    var
      frmMain: TfrmMain;
    
    implementation
    
    {$R *.dfm}
    
    function GetGUID():string;
    var
      ID:TGUID;
    begin
      CoCreateGuid(ID);
      Result := GUIDToString(ID);
    end;
    
    
    procedure TfrmMain.FormCreate(Sender: TObject);
    begin
      LogObj := TsfLog.Create('C:\temp\0001.TXT');
      FList := TList.Create();
    end;
    
     
    
    //启动测试
    
    procedure TfrmMain.Button1Click(Sender: TObject);
    var
      Obj:TsfLogTest;
      Index:Integer;
    begin
      for Index := 1 to 120 do
      begin
        Obj := TsfLogTest.Create(FALSE);
        FList.Add(Obj);
      end;
      Self.Timer1.Enabled := TRUE;
    end;
    
    { TsfLogTest }
    
    procedure TsfLogTest.Execute;
    var
      Msg:string;
    begin
      while(not self.Terminated) do
      begin
        Msg := IntToStr(Self.ThreadID) + #09 + 
                 GetGUID() + GetGUID() + GetGUID() + GetGUID() +
                 GetGUID() + GetGUID() + GetGUID() + GetGUID();
        frmMain.LogObj.WriteLog(Msg);
        Sleep(10);
      end;
    end;
    
    procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
    var
      Index:Integer;
      Obj:TsfLogTest;
    begin
      for Index := 0 to FList.Count - 1 do
      begin
        Obj:= TsfLogTest(FList.Items[Index]);
        Obj.Terminate();
      end;
      Sleep(100);
    end;
    
    procedure TfrmMain.Timer1Timer(Sender: TObject);
    var
      AFileName:string;
    begin
      AFileName :=  'C:\Temp\' +  FormatDateTime('YYYYMMDD_hhmmss_zzz',Now()) + '.TXT';
      LogObj.FileName :=  AFileName;
    end;
    
    end.
  • 相关阅读:
    offer
    ubuntu中常用软件的安装
    个人学习连接
    七月在线算法班作业
    程序设计1
    Adaptive Decontamination of the Training Set: A Unified Formulation for Discriminative Visual Tracking
    correlation filters in object tracking2
    correlation filters in object tracking
    学习网站
    从熵到交叉熵损失函数的理解
  • 原文地址:https://www.cnblogs.com/lwm8246/p/2205775.html
Copyright © 2011-2022 走看看