我遇到一些与线程有关的奇怪行为,所以我猜有些事情我做错了或者我不明白的事情。
我的应用程序(Delphi Berlin)有两个过程:服务和控制台应用程序。他们通过socket(Indy)进行通信。
每个进程都有一个专门用于通信的线程。
当我需要读取/写入主线程和通信线程使用的变量时,我使用TCriticalSection。
我也大量使用日志。可以通过主线程和通信线程写入日志(按进程一个日志文件)。
所以当我想在日志文件中写一个跟踪时,我正在做的是使用变量TCriticalSection来防止主线程和通信线程同时写入日志文件:
Procedure TApp.trace(logLevel : byte; procName , pi_str: string);
var F: textfile;
LogFileName: String;
vl_log : Boolean;
vc_LogHeader : String;
th, thcurrent : TTh;
begin
if GetLog() then begin // False if log is deactivated
for th := Low(TTh) to High(TTh) do begin
if TThread.CurrentThread.ClassName = ThreadsLog.Name[th] then begin
thcurrent := th;
break;
end;
end;
if ThreadsLog.LogLevel[thcurrent] < logLevel then exit;
LogFileName := gc_tmp + WinProc.Name[WHO_AM_I] + '.log';
vc_LogHeader := '[' + GetLogTime + ' ' + ThreadsLog.Name[thcurrent] + ' ' + procName + ' ' + IntToStr(logLevel) + ']';
if Length(vc_LogHeader) < 60 then vc_LogHeader := vc_LogHeader + StringOfChar (' ', 60 - Length(vc_LogHeader) );
LockTrace.Acquire;
try
try
{$IFDEF MACOS}
AssignFile(F, LogFileName, CP_UTF8);
{$ELSE}
AssignFile(F, LogFileName);
{$ENDIF}
if FileExists(LogFileName) then Append(F) else Rewrite(F);
{$IFDEF MACOS}
Writeln(F, UTF8String(vc_LogHeader + AnsiString(pi_str)));
{$ELSE}
Writeln(F, vc_LogHeader + pi_str);
{$ENDIF}
CloseFile(F);
except
on e : exception do begin
dbg(LogFileName + ' ' + e.Message);
end;
end;
finally
lockTrace.Release;
end;
end;
end;
function TApp.GetLog() : boolean;
begin
gl_logLock.Acquire;
try
result := gl_log;
finally
gl_logLock.Release;
end;
end;
但有时,某些行不会写入文件。 但是dbg(LogFileName +''+ e.Message)没有执行,因为它应该在另一个日志文件中写入并且该文件保持为空。所以似乎没有例外被解雇。
这样可以使用TCriticalSection吗? 我对TCriticalSection的理解是,它放置了一个锁,以便其他线程试图放置自己的锁必须等到它被释放。是吗?
我想我可以使用一个变量或几个变量TCriticalSection。如果我只使用一个变量,则会有更多存在锁定的情况,因此需要等待更多时间。如果我对每个共享变量使用一个TCriticalSection,那么锁定会更少,因此性能更好。这样对吗 ?
感谢您的任何更正或澄清。
答案 0 :(得分:1)
您的代码存在许多问题,而不是所有与线程/关键部分相关的问题。
function TApp.GetLog() : boolean;
begin
gl_logLock.Acquire;
try
result := gl_log;
finally
gl_logLock.Release;
end;
end;
上述锁码无用,不提供任何保护。读取布尔变量已经是原子的。这也是对如何使代码线程安全的常见误解的症状。
for th := Low(TTh) to High(TTh) do begin
if TThread.CurrentThread.ClassName = ThreadsLog.Name[th] then begin
thcurrent := th;
break;
end;
end;
if ThreadsLog.LogLevel[thcurrent] < logLevel then exit;
在上文中,如果循环结束而 if 条件评估为True
,则thcurrent
将未初始化,从而导致未定义的行为。任何从AV异常到事情都没有达到预期效果的东西。
很可能ThreadsLog.LogLevel[thcurrent] < logLevel
可以评估为True
(和Exit
),而不会针对某些未定义的thcurrent
值触发AV。
另请注意,循环遍历线程并进行字符串比较是检查当前线程的一种非常低效的方法。目前还不清楚你想要实现什么,但你应该能够从当前的线程ID中找出某些东西。
您说dbg(LogFileName + ' ' + e.Message);
未被调用。好吧,有很多原因可能无法调用。你必须弄清楚应用哪个(1个或多个)。
Exit
。GetLog()
可能会返回False
。IOResult
手动检查它们。dbg
,但本身也可能以某种方式失败。