(I)线程的中止
    看了几本书,提到线程的中止主要有三种途径:线程所属进程中止;线程内部调用EndThread函数;线程外部调用TerminateThread函数。
   1. 线程所属进程中止这一条似乎可有可无?(问题一)事实上每次应用程序关闭之前都还要检查是否还有未结束的线程,如果还有线程没结束比如正在进行数据库等操作,则关闭应用程序可能会出问题:( 对于这个问题,一直搞不清楚,有没有必要在应用程序结束前检查并结束所有线程?(问题二)
    2.EndThread函数 当线程函数Excute()执行完毕,它会调用Delphi的标准例程EndThread,这个例程再调用A P I函数E x i t T h r e a d ( )。由ExitThread() 来清除线程所占用的栈。按照这种说法,我们在线程函数中直接调用EndThread(0)应该可以直接结束线程执行,并且释放了线程所占用的资源?(问题三:是这样的吧,我还没来得及试)这样就可以结合线程的Terminated属性,有如下代码了:
在线程函数中频繁检查Terminated属性
if(self.Terminated or Application.Terminated) EndThread(0)
    3.TerminateThread函数是Win32API函数,其声明如下:function Te r m i n a t e T h r e a d ( h T h r e a d : T H a n d l e ; d w E x i t C o d e : D W O R D ) ;  T T h r e a d的H a n d l e属性可以作为第一个参数,因此,Te r m i n a t e T h r e a d ( )常这样调用:
Te r m i n a t e T h r e a d ( M y H o s e d T h r e a d . H a n d l e , 0 )如果选择使用这个函数,应该考虑到它的负面影响。首先,此函数在Windows NT与在Windows 95/98下并不相同。在Windows 95/98 下,这个函数能够自动清除线程所占用的栈;而在Windows NT下,在进程被终止前栈仍然保留。其次,无论线程代码中是否有t r y. . . f i n a l l y块,这个函数都会使线程立即停止执行。这意味着,被线程打开的文件没有被关闭、由线程申请的内存没有被释放等情况。而且,这个函数在终止线程的时候也不通知D L L,当D L L关闭时,这也容易出现问题(摘自Steve Teixeira和Xavier Pacheco的Delphi5开发人员指南,问题四:不知道这种情况对于win2000是否适用?)
    4.关于waitfor方法 等待线程结束可以调用它的WaitFor方法。不过有些不解之处。比如Delphi5开发人员指南中有这样一段代码:
for i:=ThreadList.count-1 downto 0 do begin
  TDrawThread(ThreadList[i]).Terminate;
  TDrawThread(ThreadList[i]).waitfor;
  //(问题五:如果线程设置了FreeOnTerminate:=true,则调用Terminate之后,有没有可能线程马上被终止且释放了资源,此时若调用waitfor会不会出问题?)
end;  综上,如果结束一个线程比较稳妥的方式是这样:首先,在线程函数中频繁检查Terminated属性,发现为True时,调用EndThread(0)方法结束线程;在需要的时候调用线程的Terminate方法将线程的Terminated属性设置为true即可达到结束线程的问题;为了确保线程已经终止,使用waitfor方法进行确认。II 线程的同步
  1.临界区 所谓临界区是指源代码中禁止两个线程同时操作的部分,即一次只有一个线程进入该区域,就是一次只能由一个线程来执行的一段代码。不过一直弄不明白临界区到底保护哪些资源,似乎只是定义中描述的“禁止两个线程同时操作”的代码。我的理解中,临界区似乎只能同步同一文件中定义的线程类的多个实例,它们才有进入同一临界区的可能。如果有线程类ThreadA、ThreadB,分别在不同的文件中被定义,即使在每个文件中都有同样命名为CriticalOK的全局临界区变量,似乎也不能达到同步的作用,互相之间应该没有任何影响。如果两个线程使用同一个ADOConnection对象执行数据库操作,则由于两个线程不能很好的同步,有可能造成的数据库占线。(问题六:这段描述自己感觉也不是很清楚,望各位高手指点迷津)
  2.互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步(问题六补充:临界区不能跨进程,恐怕也不能跨线程(文件)吧)。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。
   根据上面描述引出问题七:两个线程同时使用同一个ADOConnection连接数据库进行操作会不会引起连接占线?这个和线程无关了,我在使用的时候发现会出现连接占线的情况,这时在应用程序的所有线程,包括主线程中通过使用互斥保证同一时间只有一个线程使用ADOConnection连接数据库操作--这样做法有没有太罗索了?是否必要?这样子的话,似乎程序一起动就应该创建这个互斥对象了。
   书中范例在程序中使用Wa i t F o r S i n g l e O b j e c t ( )来防止其他线程进入同步区域的代码,该函数等待返回的代码为WA I T _ O B J E C T _ 0,互斥对象处于发信号状态;但是如果返回了WA I T _ A B A N D O N E D,则互斥对象归当前状态所有且被设为非发信号状态。问题八:看书时,认为如果返回WAIT_ABANDONED,似乎也应该允许进入当前线程的同步区域呀?  3.信号量 它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。目前编写的一些程序还没有复杂到需要使用信号量的地步,所以没有什么实际操作的经验。只是对信号量对象的名称、以及互斥对象的名称有些疑虑,问题九:信号量对象、互斥对象既然可以用于跨进程的线程同步,如果两个应用程序为自己的信号量命名了同一名称,但本意并不是想跨进程同步的,这时候由于信号量名称的缘故,岂不是会出乱子了?
这两天为了线程程序的问题,翻来覆去的看了几本书中关于线程的介绍。依然有些模糊,准备写代码测试一下子,也希望各位高手不吝赐教。

解决方案 »

  1.   

    这么长!楼主首先应该明确一个概念, Delphi 的 Thread 对象不等同于实际的线程!
      

  2.   

    Thread对象的构造方法里有BeginThread,其实现是通过调用Win32API函数CreateThread并返回实际线程的句柄;Thread对象的FHandle就是保存了实际线程的句柄。这样算是明确了吗?不可否认很长,不过线程的相关东西本来就是很多的……
      

  3.   

    呵呵,怎么感觉楼主话里有些火药味啊?我仅就一个问题说说看法:
    ----------------------------------------------
    2.EndThread函数 当线程函数Excute()执行完毕,它会调用Delphi的标准例程EndThread,这个例程再调用A P I函数E x i t T h r e a d ( )。由ExitThread() 来清除线程所占用的栈。按照这种说法,我们在线程函数中直接调用EndThread(0)应该可以直接结束线程执行,并且释放了线程所占用的资源?
    ----------------------------------------------
    在 TThread 派生类的 Execute 方法中调用 ExitThread(),的确能使线程退出,但是这样做有可能出现内存泄漏。在什么样的情况下会出现内存泄漏呢? 当你把 TThread 对象的 FreeOnTerminate 设为 True 时!
    原因如下:真正的“线程函数”应该是 System 单元的 ThreadWrapper 全局函数,ThreadWrapper 又调用了 Classes 单元的 ThreadProc 全局函数,在 ThreadProc 中才调用了相应 TThread 对象的 Execute 方法。 
    在一般情况下,之所以把 FreeOnTerminate 设为 True 能够自动释放 TThread 对象,是因为在 ThreadProc 中有如下代码:
      FreeThread := Thread.FFreeOnTerminate; // FreeThread: Boolean;
      .....
      if FreeThread then Thread.Free;  // Thread: TThread
    但是如果在 Execute 方法中调用 ExitThread() 使线程退出,那么上述代码就根本不会执行。这样的话,线程结束了,但 TThread 对象并没有被释放,从而造成内存泄漏。这也是我说“Thread 对象不等同于实际的线程”的原因!
      

  4.   

    呵呵,偶不当老大好多年,不知火药何味了:)感谢leapmars(流铭) 的指点,Thread对象不等同于实际线程又有了一个新的认识。
    function ThreadProc(Thread: TThread): Integer; 
    var 
    FreeThread: Boolean; 
    begin 
    try 
    Thread.Execute; 
    finally 
    FreeThread := Thread.FFreeOnTerminate; 
    Result := Thread.FReturnValue; 
    Thread.FFinished := True; 
    Thread.DoTerminate; 
    if FreeThread then Thread.Free; 
    EndThread(Result); 
    end; 
    end; 从上述代码看来,如果使用EndThread(0)来结束线程则Thread.DoTerminate事件指定的方法得不到执行;FreeOnTerminate=true时,Thread.Free没有得到执行。不过,如果FreeOnTerminate=false,在Thread.Execute中用如下代码,是不是就没有什么问题了?
    if(self.Terminated) then begin
            self.DoTerminate;
            EndThread(0);
    end;
      

  5.   

    其中问题五:
    试了一下,真的会出问题。所以如果需要用到waitfor则最好不要将FreeOnTerminate属性设置为True
      

  6.   

    不知楼主为什么非要在 TThread 对象的 Execute 方法中调用 EndThread ? 当程序从 Execute 返回到 ThreadProc 后,ThreadProc 就会自己调用 EndThread 啊。你这样做,岂不多此一举?
      

  7.   

    我也不是非得调用EndThread不可,只是试着弄清楚里面的关系到底是怎样的;事实上我已经决定非特殊情况不用EndThread了。楼上周兄所言全局变量,我觉得似乎无甚必要。只需要在线程函数中检测Terminated属性,为True时立即终止线程对象;如果要中止正在运行的线程,则调用线程对象的Terminate方法,该方法将线程对象的Terminated属性设为True,通知线程中止运行
      

  8.   

    楼上的weidegong(weidegong) 兄弟不要忘了,VCL对多线程的支持比较有限,仅能很好的支持工作者线程,对用户UI线程的支持不是很好,而且多线程同步访问VCL对象时,实际上上VCL同步函数内部干了偷天换日的勾当,并不是真正意义上的多线程同步。
      

  9.   

    周兄可否详细说说?是指多线程同步的时候,使用Terminate不能马上停止线程吗?用全局变量的话,从封装的角度来看就不是很好,增加了耦合;只能在线程的create方法中增加一个变量参数,把此全局变量引入线程对象;不过使用全局变量的效果和使用Thread.Terminated属性有什么差异吗?除非一个Thread对象不对应一个实际线程的时候?