网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> Delphi编程 >> 文章正文
  DELPHI线程类挂起的问题            【字体:
DELPHI线程类挂起的问题
作者:佚名    文章来源:不详    点击数:    更新时间:2007-8-3    
 
 
DELPHI线程类挂起的问题
                                                                                           ——赵海杰
对于用过Delphi的TThread类的兄弟应该很了解,在Tthread中有个Suspended属性,如果Suspended属性为True时,则表示线程是挂起状态,为False则表示线程是运行状态。
正在装载数据……
但是有些人常会发现:如果频繁的挂起和唤醒线程时,就会出现,线程已经挂起而Suspended为False的现象。
问题发现:
如果我们想写一个程序,其中想判断一个线程是否是挂起状态,是则唤醒去处理一件事情,否则调用别的线程。每个线程做完事情后都会自动挂起。代码实现如下:
控制线程:
If thread. Suspended = true then
Begin
   // 是空闲状态,唤醒,
   Thread.resume;
End
Else
Begin
   // 不是,等待。
   wait();
End;
// 线程处理函数
procedure thread.Execute ;
begin
while terminated = false do
begin
   dowork;
    // 做完后挂起。
   suspend;
end;
end;
 
如果控制线程事情很多,对线程调起很频繁,时间不长,线程就会挂起,而Suspended属性一直为false,整个系统就可能停掉。
问题分析:
我们可以先看一下Tthread线程的挂起和唤醒函数,Delphi中有源代码。只看windows的。
$IFDEF MSWINDOWS}
procedure TThread.Suspend;
begin
 FSuspended := True;
 CheckThreadError(Integer(SuspendThread(FHandle)) >= 0);
end;
 
procedure TThread.Resume;
var
 SuspendCount: Integer;
begin
 SuspendCount := ResumeThread(FHandle);
 CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
end;
{$ENDIF}
可以看到:在挂起时,是首先把Fsuspended设置为true.然后进行挂起操作。而,唤醒时,先调用ResumeThread,成功后,再把Fsuspended设置成false。
 
如果是多线程同时运行的情况下,我看可以考虑一下下面情景:
当线处理线程挂起时,我们执行唤醒操作,SuspendCount := ResumeThread(FHandle);这句结束后,返回值一定是1(可以参考ResumeThread的说明),然后线程调度,主控线程线程挂起,而处理线程运行(已经唤醒),处理线程处理完自己的事情后,接着进行自动挂起操作(这些操作要全部在一个时间片内完成),首先把Fsuspended,然后SuspendThread也会成功的。此时处理线程已然挂起,时间片再次切换到主控线程,执行以下语句:CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
便又把Fsuspended改成false,而此时的线程已经挂起,然后,出现了我们上面提到的情况。
解决方案:
我们可以对挂起和唤醒操作加临界区限制一下,同时我们可能还会注意一下,如果对于挂起操作用EnterCriticalSection和LeaveCriticalSection时要注意一下,如果LeaveCriticalSection放在CheckThreadError(Integer(SuspendThread(FHandle)) >= 0)下面的话,那么如果是线程自动挂起的话,LeaveCriticalSection这句就可能执行不到了,且由于我们只需要保证唤醒是原子操作便可以了(原因我们下面说明),因此我们改成下面的模式:
procedure TThread.Suspend;
begin
  EnterCriticalSection(@Se);
 FSuspended := True;
 CheckThreadError(Integer(SuspendThread(FHandle)) >= 0);
   LeaveCriticalSection(@Se);
end;
 
procedure TThread.Resume;
var
 SuspendCount: Integer;
Begin
EnterCriticalSection(@Se);
LeaveCriticalSection(@Se);
 SuspendCount := ResumeThread(FHandle);
 CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
end;
这样我们就能保证唤是原子操作了,不会出现线程已经挂起而Suspended却为False的情况了,但是这种方案还有它的不足之处,我们下面进行行讨论。
延伸讨论:
首先,我们先讨论挂起操作不为原子操作的情况:如果挂起时FSuspended := True先执行,而挂起操作没有执行时,时间片切换后,主控线程执行唤醒,唤醒函数返回值为0,唤醒操作后不会改变Fsuspended的值,然后当再执行挂起操作时,线程挂起且Fsuspended为true,是没有问题的。注意:状态是没有问题的,可能从用户处理的逻辑上是有问题的,因为我们唤醒线程是处理业务的,而此时业务处理函数根本没有处理,就直接挂起了,所以挂起操作不为原子操作解决了状态不对的现象,但还是留了一个“尾巴”。解决这个“尾巴”问题,就必须使用挂起也为原子操作了,我不想作深入讨论,只是提供两个解决方案:1,专门创建一个线程管理线程的挂起和唤醒操作,这样就不存在自己挂起自己的现象,LeaveCriticalSection就可以放在挂起函数下面了,比较麻烦。2,业务处理线程中自己处理,较为简单些。
   wait();
End;
// 线程处理函数
procedure thread.Execute ;
begin
while terminated = false do
begin
   dowork;
    // 做完后挂起。
   suspend;
end;
end;
 
如果控制线程事情很多,对线程调起很频繁,时间不长,线程就会挂起,而Suspended属性一直为false,整个系统就可能停掉。
问题分析:
我们可以先看一下Tthread线程的挂起和唤醒函数,Delphi中有源代码。只看windows的。
$IFDEF MSWINDOWS}
procedure TThread.Suspend;
begin
 FSuspended := True;
 CheckThreadError(Integer(SuspendThread(FHandle)) >= 0);
end;
 
procedure TThread.Resume;
var
 SuspendCount: Integer;
begin
 SuspendCount := ResumeThread(FHandle);
 CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
end;
{$ENDIF}
可以看到:在挂起时,是首先把Fsuspended设置为true.然后进行挂起操作。而,唤醒时,先调用ResumeThread,成功后,再把Fsuspended设置成false。
 
如果是多线程同时运行的情况下,我看可以考虑一下下面情景:
当线处理线程挂起时,我们执行唤醒操作,SuspendCount := ResumeThread(FHandle);这句结束后,返回值一定是1(可以参考ResumeThread的说明),然后线程调度,主控线程线程挂起,而处理线程运行(已经唤醒),处理线程处理完自己的事情后,接着进行自动挂起操作(这些操作要全部在一个时间片内完成),首先把Fsuspended,然后SuspendThread也会成功的。此时处理线程已然挂起,时间片再次切换到主控线程,执行以下语句:CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
便又把Fsuspended改成false,而此时的线程已经挂起,然后,出现了我们上面提到的情况。
解决方案:
我们可以对挂起和唤醒操作加临界区限制一下,同时我们可能还会注意一下,如果对于挂起操作用EnterCriticalSection和LeaveCriticalSection时要注意一下,如果LeaveCriticalSection放在CheckThreadError(Integer(SuspendThread(FHandle)) >= 0)下面的话,那么如果是线程自动挂起的话,LeaveCriticalSection这句就可能执行不到了,且由于我们只需要保证唤醒是原子操作便可以了(原因我们下面说明),因此我们改成下面的模式:
procedure TThread.Suspend;
begin
 EnterCriticalSection(@Se);
 FSuspended := True;
 CheckThreadError(Integer(SuspendThread(FHandle)) >= 0);
   LeaveCriticalSection(@Se);
end;
 
procedure TThread.Resume;
var
 SuspendCount: Integer;
Begin
EnterCriticalSection(@Se);
LeaveCriticalSection(@Se);
 SuspendCount := ResumeThread(FHandle);
 CheckThreadError(SuspendCount >= 0);
 if SuspendCount = 1 then
    FSuspended := False;
end;
这样我们就能保证唤是原子操作了,不会出现线程已经挂起而Suspended却为False的情况了,但是这种方案还有它的不足之处,我们下面进行行讨论。
延伸讨论:
首先,我们先讨论挂起操作不为原子操作的情况:如果挂起时FSuspended := True先执行,而挂起操作没有执行时,时间片切换后,主控线程执行唤醒,唤醒函数返回值为0,唤醒操作后不会改变Fsuspended的值,然后当再执行挂起操作时,线程挂起且Fsuspended为true,是没有问题的。注意:状态是没有问题的,可能从用户处理的逻辑上是有问题的,因为我们唤醒线程是处理业务的,而此时业务处理函数根本没有处理,就直接挂起了,所以挂起操作不为原子操作解决了状态不对的现象,但还是留了一个“尾巴”。解决这个“尾巴”问题,就必须使用挂起也为原子操作了,我不想作深入讨论,只是提供两个解决方案:1,专门创建一个线程管理线程的挂起和唤醒操作,这样就不存在自己挂起自己的现象,LeaveCriticalSection就可以放在挂起函数下面了,比较麻烦。2,业务处理线程中自己处理,较为简单些。
 


本文来源:http://blog.csdn.net/chaohaijie/archive/2007/07/02/1674985.aspx
站内文章搜索 高级搜索
文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     在delphi中使用xml文档有…
     初探delphi 7 中的插件编…
     delphi 2006(dexter) & …
     获得windows的版本信息。
     “序列号输入助手”源代…
     rs232串口通讯模块
     ado方式下判断数据表是否…
  • Ant入门-配置和使用     选…

  • 浅析Spring框架下PropertyPl…

  • SPRING+STRUTS+HIBERNATE登录…

  • MVP——Model-Viewer-Presen…

  • C++ Object Model

  • constructor and destructor

  • 绑定HGE到AngelScript脚本系…

  • delegate C#关键字 (委托类型…

  • Boyer-Moore String Searchi…

  • 【游戏制作基础】网络游戏设…

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David