Delphi Thread and TCriticalSection

–2 votes
asked Mar 29, 2017 by user2244705

I encounter some strange behaviors with threads so I guess there is something I'm doing wrong or something I don't understand.

My application (Delphi Berlin) has two processes : a service and a console app. They communitcate via socket (Indy).

Each process has a thread dedicated to communication.

I use TCriticalSection when I need to read/write variables used by main thread and by communication Thread.

I also make intensive usage of log. The log can be written (one log file by process) by main thread and by communication thread.

So what I'm doing when I want to write a trace in log file is to use a variable TCriticalSection to prevent main thread and commmunication thread to write to the log file at the same time:

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;

However sometimes, some lines are not written to the file. But dbg(LogFileName + ' ' + e.Message) does not execute cause it is supposed to write in another log file and this file stays empty. So no exception seems to be fired.

Is it possible to use TCriticalSection this way ? What I understand about TCriticalSection, is that it puts a lock so others threads trying to put their own lock have to wait until it is released. Is that right ?

I guess I can use one variable or several variables TCriticalSection. If I use only one variable, there will be more cases where a lock exists so more time to wait. If I use one TCriticalSection per shared variable, there will be less locks so better performances. Is it right ?

Thanks for any correction or clarification.

1 Answer

+1 vote
answered Nov 8 by disillusioned

There are many problems with your code, not all thread / critical section related.


function TApp.GetLog() : boolean;
begin gl_logLock.Acquire; try result := gl_log; finally gl_logLock.Release; end;
end;

The above lock code is useless it doesn't provide any protection whatsoever. Reading a boolean variable is already atomic. It's also symptomatic of a common misunderstanding of how to make code thread-safe.

  • Locks are intended to protect access to data.
  • The above pattern is often incorrectly used to protect access to an object.
  • But once the calling code is able to start using the object, you're already outside the lock.
  • I.e the underlying data of the object is no longer protected from concurrent thread access.

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;

In the above, if the loop ever ends without the if condition evaluating to True, thcurrent will be uninitialised leading to undefined behaviour. Anything from AV exceptions to things just not behaving as you'd expect.

Quite possibly ThreadsLog.LogLevel[thcurrent] < logLevel could evaluate to True (and Exit) without triggering an AV for some undefined values of thcurrent.

Also note that looping through your threads and doing string comparisons is a pretty inefficient way to check your current thread. It's not clear what you're trying to achieve, but you should be able to figure certain things out simply from the current thread id.


You say dbg(LogFileName + ' ' + e.Message); is not called. Well there are many reasons it might not be called. You'll have to figure out which (1 or multiple) apply.

  • You could Exit early.
  • GetLog() might return False.
  • Any exception before the try..except block won't get there.
  • If you've disabled IO errors, an exception won't be raised by old-style file operations. You would have to manually check them using IOResult.
  • And of course dbg might be called, but could itself also fail in some way.
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
...