问题描述
我发现 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;
解决方法
根据您的日志,ltime
为 468528329
,GetTickDiff64(ltime,Ticks64())
返回 18446744073709551600
。鉴于 GetTickDiff64()
的简单实现(其中 TIdTicks
是 UInt64
):
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
的唯一方法是 ANewTickCount
是 18446744074178079929
或 468528313
。
由于 VCL 仅在 Windows 上运行,并且在 Windows 上 Ticks64()
只是对 Vista 和更高版本上的 Win32 GetTickCount64()
函数的一个薄包装,因此 Windows 不太可能产生如此大的天文数字当前滴答计数器的数字如 18446744074178079929
(即从启动开始的 213503982340 天)。所以它肯定返回了 468528313
,这是更合理的(即启动后仅 5.4 天)。这比 ltime=468528329
少 16 毫秒,因此 GetTickDiff64()
会假设 Windows 的滴答计数器已超过 High(UInt64)
并返回到 0(这对于我们一生中永远要做的 64 位滴答计数器)。
因此,您需要调试代码并弄清楚 Ticks64()
/Windows 可能如何返回 468528329
,然后返回 468528313
。我怀疑它真的没有这样做,而且您的代码中更有可能存在一个错误,我们无法看到哪个错误将错误的值存储到 TMyClass.lastSetTime
中。
话虽如此,您可能会考虑摆脱 TCriticalSection
的开销,而是使用 TInterlocked
以原子方式读取/写入您的 UInt64
成员。
或者,尝试使用 Delphi 自己的 TStopWatch
而不是手动跟踪刻度。