您现在的位置:首页 >> 基础算法 >> window基础 >> 内容

Delphi中多线程分析详解(2)

时间:2011/9/3 15:35:57 点击:

3. 线程中常见的问题。

   1) 回调函数引起的死锁。

      A回调线程B中的函数,而在线程B中,再去对线程A进行操作(比如删除A)。

发生的现象:程序死掉。

 

     2) 使用同一资源未加保护引起问题。

        A和B同时去对窗体上进行绘图操作,界面可能花掉,也可能黑掉。

600) this.width = 600;">

出现的现象:界面不再刷新,变成黑色。(最好不要在子线程中去更新界面UI,可以使用消息来更新)

 

  3) 线程锁使用不当造成死锁。

    线程A利用线程锁锁住资源A后,再去试图访问资源B,线程B利用线程锁锁住资源B后试图去访问资源A。这样就发生了线程互锁。

600) this.width = 600;">

程序结果:线程死掉。

 

  4) 未加线程保护产生异常。

     线程A获取到了对象X(步骤1)的引用后被挂起(步骤2),而接下来线程B却删除了X(步骤3),线程A再次唤醒后访问对象X出错(步骤4)。这个问题是多线程中最容易被忽略的地方,也是异常最可能发生的情况。

600) this.width = 600;">
程序结果:线程异常。

 

  5) 消息在线程同步中的问题。

先说说消息的一些基本问题(有关消息的处理部分在Windows 2000源码private\ntos\w32\ntuser\kernel\input.c文件中):

消息队列的建立:线程在刚建立的时候,是没有消息队列的。当有界面UI操作函数被调用的时候(比如CreateWindow),Windows就会为该程序建立一个消息队列,同样,通过调用PeekMessage/GetMessage可以强制操作系统为线程建立一个消息队列(参看MSDN关于PostThreadMessage的说明)。

消息的正常处理流程:线程通过GetMessage/PostMessage获取消息,然后通过TranslateMessage进行字符转换,接下来,通过User32.dll模块的帮助最后调用到相应窗口的窗口过程(RegisterClass时传入的窗口过程)。

消息重入问题:所谓的消息重入,就是在消息处理过程中再调用SendMessage发送消息给目标窗口。如果控制不好,就会引发异常。一个简单的例子:在WM_PAINT中再去SendMessage(hWin, WM_PAINT, 0, 0)。其结果就是堆栈溢出异常了(stack overflow)。(相当于WndProc在进行无限递归),Delphi代码如下(可以自己感受一下“Stack Overflow”是怎么一回事,等异常后调出CallStack看看^_^):

procedure OnPaint(var tMsg: TMessage); message WM_PAINT;                 // Interface

// Implementation

procedure TForm1.OnPaint(var tMsg: TMessage);

begin

  SendMessage(Self.Handle, WM_PAINT, 0, 0);

end;

SendMessage & PostMessage: SendMessage发送一个消息给窗口,同时,操作系统会去直接调用窗口的窗口过程而不经过线程的消息队列。而PostMessage则仅仅是将消息投递到消息队列,应用程序通过GetMessage/PeekMessage获取消息处理后再交给系统分发消息。

600) this.width = 600;">

消息在多线程中的问题:

分析一个具体过程说明在多线程中因消息而引起的问题:

线程A(主线程)通过GetMessage->DispatchMessage,接下来通过User32模块的帮助调用WndProc进行消息处理的过程对RichEdit中插入一张图片。其步骤如下:

1. 在RichEdit中定位要插入的位置(X, Y)。RichEdit->SetSel(X, Y)

2. 创建OLE对象。

3. 获取ClientSite接口插入对象。

 

线程B通过SendMessage调用WndProc要求在RichEdit的末尾添加一段文字。其步骤如下:

1. 定位到RichEdit末尾。RichEdit->SetSel(-1, -1);

2. 调用RichEdit->ReplaceSel(sText)插入文本。

 

假如线程A在步骤1刚运行完毕后,其运行时间片结束,线程被挂起,当前的状态被保存到线程A的堆栈中。线程B开始运行,线程B插入文本完成返回,线程A重新被唤醒,开始执行步骤2,3, 而这时,线程B已经改变了当前的插入位置,线程A并不知道,于是,就会出现插入的图片错位现象。

归根结底,是线程的同步问题。

结论:尽量用PostMessage而不是SendMessage。

6) 异常引起的问题。

   看这段代码:

   CCriticalSection  m_cLock;

  

   m_cLock.Lock;

   // Do something

   m_cLock.Unlock();

 

    初看是没有什么问题,但是,如果我们在DoSomeThing的时候产生了异常,那么UnLock代码将不会被执行,于是,线程锁将一直处与加锁状态,其他线程将无法访问。

m_cLock.Lock;

try

{

         // Do something

         m_cLock.Unlock();

}

catch(…)

{

m_cLock.Unlock();

}

 

在Delphi中处理这种情况很简单:

m_cLock.Lock;

try

  // Do something

finally

 m_cLock.Unlock;

end;

 

以上是我在多线程中所遇到的一些问题的总结,希望能对大家有用。

上一页123下一页

作者:James.Zhai 来源:转载
共有评论 0相关评论
发表我的评论
  • 大名:
  • 内容:
  • 盒子文章(www.2ccc.com) © 2024 版权所有 All Rights Reserved.
  • 沪ICP备05001939号