indy gettickdiff64() 18446744073709551600 问题

问题描述

我发现 gettickdiff64 函数有时会导致 18446744073709551600(或 18446744073709551601)并导致 程序运行不正确。 通常不会有大于 300000 的结果

这可能是关于什么的? 我是否应该始终针对此问题进行额外检查?

它是 32 位 VCL 应用程序。 我使用 Delphi 10.4.1(它的 indy 版本 10.6.2.0) 运行于:64 位 Windows Server 2012 R2 Foundation / intel xeon cpu E3-1225 v5 3.3 Ghz。

代码结构如下:

TMyClass = class
 private
  //.............
  lastSetTime: uint64;
  critic: TCriticalSection;
 public
  //.............
  procedure setLastSetTime( ltime: uint64 );
  function getLastSetTime: uint64;
end;

procedure TMyClass.setLastSetTime( ltime: uint64 );
 begin
  critic.enter;
  try
    lastSetTime := ltime;
  finally
   critic.leave;
  end;
 end;

function TMyClass.getLastSetTime: uint64;
 begin
  critic.enter;
  try
    result := lastSetTime;
  finally
   critic.leave;
  end;
 end;


...........


procedure controlAll(); //------>this is called from within thread every 5 minutes
 var oki: boolean;
     starttime,tdiff,ltime: uint64;
     i: integer;
     myC,sC: TMyClass;
 begin
   oki := false;
   starttime := ticks64();
   while ( oki = false ) and ( gettickdiff64( starttime,ticks64 ) < 40000 ) do
   begin
     //.........
     //.........
     sC := nil;
     with myClassList.LockList do
     try
       if count > 0 then //---> has about 50000
       begin
         i := 0;
         while i < count do
         begin
           myC := TMyClass( items[ i ] );
           ltime := myC.getLastSetTime();
           tdiff := gettickdiff64( ltime,ticks64() );
           if tdiff > 50000 then
           begin
             logToFile( tdiff.ToString + ' ' + ltime.ToString );  //-----> every 5 minutes 50-60 log lines occur like this: 18446744073709551600 468528329
             //..........
             //.........
             sC := myC;
             delete( i );
             break;
           end; 
           inc( i );
         end;
       end;
     finally
       myClassList.UnlockList;
     end; 

     if sC = nil then oki := true
     else
     begin
       //..........
       //..........
     end;
   end;
 end;

设置该值的代码结构如下。

classListArray 将所有 TMyClass 类型的类按服务器和频道号分组。 myClassList 将 TMyClass 类型的所有类一个一个地连接起来,而不进行分组。 classListArray 用于花费更少的 cpu 并更快地处理。 这两个列表在访问类时不会相互保护。 只有在添加删除类时才能相互保护。

classListArray: array[ 1..250,1..12 ] of TThreadList;

//.................

procedure ServerExecute(AContext: TIdContext);
 var Ath: TMypeer;
     severNum,channelNum,clientNum,i,j,num: integer;
     pSize: word;
     stream: Tmemorystream;
     packageNum: byte;
 begin
   try
      Ath := TMypeer( AContext );

      serverNum  := Ath.getServerNum();
      channelNum := Ath.getChannelNum();

      Ath.SendQueue();

      if AContext.Connection.IOHandler.InputBufferIsEmpty then
        if not AContext.Connection.IOHandler.CheckForDataOnSource( 50 ) then Exit;
   
      clientNum := AContext.Connection.IOHandler.ReadInt32( false );
      pSize := AContext.Connection.IOHandler.ReadUInt16( false );

      stream := TMemorystream.create;
      try
        AContext.Connection.IOHandler.ReadStream( stream,pSize );

        stream.Seek( 0,soFromBeginning );
        if clientNum <> 0 then
        begin
          //...........
        end
        else
        begin 
          stream.ReadBuffer( packageNum,sizeof( packageNum ) );
          if packageNum = 10 then
          begin
            stream.ReadBuffer( num,sizeof( num ) );
            for i := 1 to num do
            begin
               stream.ReadBuffer( clientNum,sizeof( clientNum ) );
               with classListArray[ serverNum,channelNum ].LockList do
               try
                 if count > 0 then
                  for j := 0 to count - 1 do
                   begin
                     if TMyClass( items[ j ] ).getClientNum = clientNum then 
                     begin
                        TMyClass( items[ j ] ).setLastSetTime( ticks64 ); //**********
                        break;
                     end; 
                   end;
               finally
                 classListArray[ serverNum,channelNum ].unLockList;
               end;
            end;
          end
          else
          //.........
        end;
      finally
        stream.free;
      end;
   except on e:exception do
      begin
        if E is Eidexception then raise
        else
        begin
           logToFile( e.message );
           //..........
        end;
      end; 
   end;
 end;

解决方法

根据您的日志,ltime468528329GetTickDiff64(ltime,Ticks64()) 返回 18446744073709551600。鉴于 GetTickDiff64() 的简单实现(其中 TIdTicksUInt64):

function GetTickDiff64(const AOldTickCount,ANewTickCount: TIdTicks): TIdTicks;
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
  {This is just in case the TickCount rolled back to zero}
  if ANewTickCount >= AOldTickCount then begin
    Result := TIdTicks(ANewTickCount - AOldTickCount);
  end else begin
    Result := TIdTicks(((High(TIdTicks) - AOldTickCount) + ANewTickCount) + 1);
  end;
end;

此代码在给定 18446744073709551600 的情况下返回 AOldTickCount=468528329 的唯一方法是 ANewTickCount18446744074178079929468528313

由于 VCL 仅在 Windows 上运行,并且在 Windows 上 Ticks64() 只是对 Vista 和更高版本上的 Win32 GetTickCount64() 函数的一个薄包装,因此 Windows 不太可能产生如此大的天文数字当前滴答计数器的数字如 18446744074178079929 (即从启动开始的 213503982340 天)。所以它肯定返回了 468528313 ,这是更合理的(即启动后仅 5.4 天)。这比 ltime=46852832916 毫秒,因此 GetTickDiff64() 会假设 Windows 的滴答计数器已超过 High(UInt64) 并返回到 0(这对于我们一生中永远要做的 64 位滴答计数器)。

因此,您需要调试代码并弄清楚 Ticks64()/Windows 可能如何返回 468528329,然后返回 468528313。我怀疑它真的没有这样做,而且您的代码中更有可能存在一个错误,我们无法看到哪个错误将错误的值存储到 TMyClass.lastSetTime 中。

话虽如此,您可能会考虑摆脱 TCriticalSection 的开销,而是使用 TInterlocked 以原子方式读取/写入您的 UInt64 成员。

或者,尝试使用 Delphi 自己的 TStopWatch 而不是手动跟踪刻度。