Delphi线程死锁
我有时会在销毁某些线程时遇到一个死锁的问题.我试图调试问题,但是在IDE中调试时,死锁似乎并不存在,也许是由于IDE中事件的速度较低.
问题: 主线程在应用程序启动时创建多个线程.线程总是存活并与主线程同步.没有问题.当应用程序结束(mainform.onclose)时,线程将被破坏: thread1.terminate; thread1.waitfor; thread1.free; 等等. 但有时候,线程之一(将一些字符串记录到备忘录,使用同步)将在关闭时锁定整个应用程序.我怀疑线程正在同步,当我调用waitform和harmaggeddon发生,但这只是一个猜测,因为死锁从来没有发生在调试(或者我从来没有能够再现它).任何建议? 解决方法
记录消息只是Synchronize()完全没有任何意义的区域之一.您应该创建一个日志目标对象,该对象具有一个字符串列表,受关键部分的保护,并添加您的日志消息.让主VCL线程从该列表中删除日志消息,并将其显示在日志窗口中.这有几个优点:
>你不需要调用Synchronize(),这只是一个坏主意.好的副作用是你的关机问题消失了. 没有任何缺点,我可以看到 – 日志消息的顺序也保留下来. 编辑: 我将添加一些更多的信息和一些代码来玩,以便说明有更好的方法来做你需要做的事情. 从与VCL程序中的主应用程序线程不同的线程调用Synchronize()将导致调用线程阻塞,所传递的代码将在VCL线程的上下文中执行,然后调用线程将被解除阻塞并继续跑.在单处理器机器的时代,这可能是一个好主意,只有一个线程可以一次运行,但是使用多个处理器或内核,这是一个巨大的浪费,应该不惜一切代价避免.如果在8核心机器上有8个工作线程,调用Synchronize()可能会将吞吐量限制在可能的一小部分. 实际上,调用Synchronize()并不是一个好主意,因为它可能导致死锁.有一个更有说服力的理由,不要使用它. 使用PostMessage()发送日志消息将会处理死锁问题,但它有自己的问题: >每个日志字符串将导致一个消息被发布和处理,导致很多开销.一次无法处理多个日志消息. 收集日志消息的数据结构可能如下所示: type TLogTarget = class(TObject) private fCritSect: TCriticalSection; fMsgs: TStrings; public constructor Create; destructor Destroy; override; procedure GetLoggedMsgs(AMsgs: TStrings); procedure LogMessage(const AMsg: string); end; constructor TLogTarget.Create; begin inherited; fCritSect := TCriticalSection.Create; fMsgs := TStringList.Create; end; destructor TLogTarget.Destroy; begin fMsgs.Free; fCritSect.Free; inherited; end; procedure TLogTarget.GetLoggedMsgs(AMsgs: TStrings); begin if AMsgs <> nil then begin fCritSect.Enter; try AMsgs.Assign(fMsgs); fMsgs.Clear; finally fCritSect.Leave; end; end; end; procedure TLogTarget.LogMessage(const AMsg: string); begin fCritSect.Enter; try fMsgs.Add(AMsg); finally fCritSect.Leave; end; end; 许多线程可以同时调用LogMessage(),进入关键部分将序列化对列表的访问,并且在添加他们的消息之后,线程可以继续他们的工作. 这就是VCL线程知道何时调用GetLoggedMsgs()来从对象中删除消息并将它们添加到窗口的问题.一个穷人的版本将是一个定时器和民意调查.更好的方法是在添加日志消息时调用PostMessage(): procedure TLogTarget.LogMessage(const AMsg: string); begin fCritSect.Enter; try fMsgs.Add(AMsg); PostMessage(fNotificationHandle,WM_USER,0); finally fCritSect.Leave; end; end; 这仍然发生太多邮件的问题.只有当前一个消息处理完毕时,才需要发布消息: procedure TLogTarget.LogMessage(const AMsg: string); begin fCritSect.Enter; try fMsgs.Add(AMsg); if InterlockedExchange(fMessagePosted,1) = 0 then PostMessage(fNotificationHandle,0); finally fCritSect.Leave; end; end; 然而,仍然可以改进.使用定时器解决了发布的消息填满队列的问题.以下是实现这一点的小类: type TMainThreadNotification = class(TObject) private fNotificationMsg: Cardinal; fNotificationRequest: integer; fNotificationWnd: HWND; fOnNotify: TNotifyEvent; procedure DoNotify; procedure NotificationWndMethod(var AMsg: TMessage); public constructor Create; destructor Destroy; override; procedure RequestNotification; public property OnNotify: TNotifyEvent read fOnNotify write fOnNotify; end; constructor TMainThreadNotification.Create; begin inherited Create; fNotificationMsg := RegisterWindowMessage('thrd_notification_msg'); fNotificationRequest := -1; fNotificationWnd := AllocateHWnd(NotificationWndMethod); end; destructor TMainThreadNotification.Destroy; begin if IsWindow(fNotificationWnd) then DeallocateHWnd(fNotificationWnd); inherited Destroy; end; procedure TMainThreadNotification.DoNotify; begin if Assigned(fOnNotify) then fOnNotify(Self); end; procedure TMainThreadNotification.NotificationWndMethod(var AMsg: TMessage); begin if AMsg.Msg = fNotificationMsg then begin SetTimer(fNotificationWnd,42,10,nil); // set to 0,so no new message will be posted InterlockedExchange(fNotificationRequest,0); DoNotify; AMsg.Result := 1; end else if AMsg.Msg = WM_TIMER then begin if InterlockedExchange(fNotificationRequest,0) = 0 then begin // set to -1,so new message can be posted InterlockedExchange(fNotificationRequest,-1); // and kill timer KillTimer(fNotificationWnd,42); end else begin // new notifications have been requested - keep timer enabled DoNotify; end; AMsg.Result := 1; end else begin with AMsg do Result := DefWindowProc(fNotificationWnd,Msg,WParam,LParam); end; end; procedure TMainThreadNotification.RequestNotification; begin if IsWindow(fNotificationWnd) then begin if InterlockedIncrement(fNotificationRequest) = 0 then PostMessage(fNotificationWnd,fNotificationMsg,0); end; end; 该类的一个实例可以添加到TLogTarget,以在主线程中调用通知事件,但每秒钟最多打几次. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |