Delphi Thread和TCriticalSection

时间:2017-03-29 17:33:01

标签: multithreading delphi

我遇到一些与线程有关的奇怪行为,所以我猜有些事情我做错了或者我不明白的事情。

我的应用程序(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,那么锁定会更少,因此性能更好。这样对吗 ?

感谢您的任何更正或澄清。

1 个答案:

答案 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
  • try..except 块之前的任何异常都无法到达。
  • 如果您已禁用IO错误,则旧式文件操作不会引发异常。您必须使用IOResult手动检查它们。
  • 当然可以调用dbg,但本身也可能以某种方式失败。