Delphi Thread 多线程编程(6)

来源:互联网 发布:注册淘宝店铺流程图片 编辑:程序博客网 时间:2024/05/29 18:28

                                  3. 线程中常见的问题。  

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

 A回调线程B中的函数,而在线程B中,再去对线程A进行操作(比如删除A)。发生的现象:程序死掉。 


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

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

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


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

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

程序结果:线程死掉。 


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

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

程序结果:线程异常。


  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获取消息处理后再交给系统分发消息。

 

 

                 消息在多线程中的问题:

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

   线程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;
 
以上是我在多线程中所遇到的一些问题的总结,希望能对大家有用。

                 4、线程效率  

        虽然线程能够提高我们的程序的效率,然而,如果使用不当,反而会降低程序效率。特别是在线程同步的过程中,对于公共资源的读写保护部分。

       看下面的例子(假设这是一个网络多线程下载程序,一个线程负责将下载的内容保存到文件,另外几个线程负责将数据加到队列中,下面写出保存线程的示例):

typedef struct
{
   char           m_pBuf[1024];           // … Data
}TNode, *PNode;
 
private:
   CPtrList                                m_plTmpList;
CCriticalSection                 m_cLock;
 
 POSITION       posTmp;
          PNode             pItem = NULL;
 
m_cLock.Lock();
try
{
   posTmp = m_plTmpList.GetTailPosition();
 
   while(posTmp)
   {
     posPrev = posTmp;
         pItem = (PNode)m_plTmpList.GetPrev(posTmp);
      // 对于pItem进行处理,保存到磁盘      m_plTmp.RemoveAt(posPrev);
     delete pItem;
   }
  m_cLock.Unlock();
}
catch(…)
{
       m_cLock.Unlock();
}


      通常情况下,我们会使用这种方式来遍历整个列表,然而,如果我们对于pItem的处理需要很长时间,比如我们要将pItem中的数据存放到硬盘上,那么这个过程将会非常耗时。其它线程将无法访问该列表。

     那么如何才能提高效率呢?

我们可以使用两个队列,一个队列设置为下载队列,另外一个是当前保存队列。当保存队列中的所有内容全部保存到磁盘后,我们将下载队列和保存队列进行交换,即下载队列变成保存队列,保存队列变成下载队列。


总之,在线程同步操作中,提高线程效率最重要的就是减少线程公共资源操作的时间,或者是采用其它方法避免同步。


5、后记(题外话)

            当我们习惯于Windows下的RAD开发工具的时候,我们往往忽略了对于整个RDA环境的封装以及系统底层的探究,隐藏在操作系统内部的东西或机制往往被我们忽略。

           RAD工具大大提高了开发效率,然后它也助长了我们的惰性。

         很多人在抱怨大学里学习的东西都没有用,然而,现在看来,当初的很多东西都是很有用的,比如操作系统原理,数据结构和算法。

        我不是计算机的专业出身,我很庆幸当初自己对哪些东西略微了解了一些,以至于我现在理解起来一些东西不再那么困难。

        理解Windows的运行原理,Windows的主要模块作用,对于软件开发有很大的帮助。

       很长一段时间里,我被Windows的华丽外衣所迷惑,整天还沉浸在DOS下的单任务环境,迷失在Windows下的软件开发中。幸好,现在终于走出了这片森林,看到了森林的一角。

      可以欣慰的说一声:我终于找到进入软件开发的大门了。软件开发,是人和机器的交互过程

         我们想让机器更好的为我们工作,我们就需要对机器有较多的认识,同时也要对我们所处的开发环境有较多的了解。

软件开发,不仅仅是一门技术,更是一门艺术。然后,在现在,能把它当成一门艺术来看待的人已经不多了

 

原创粉丝点击