当别的子线程进入临界区后,此时主线程要进入临界区则程序会挂起,
为什么,
有什么解决办法?

解决方案 »

  1.   

    //开始阻塞
      EnterCriticalSection(FCS);
      try
        FMsgType:=AMsgType;
        FMsgStr:=AMsgStr;
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);
      finally
        //解除阻塞
        LeaveCriticalSection(FCS);
      end;
      

  2.   

    顺便帮忙看看这个帖子,也是多线程
    http://community.csdn.net/Expert/topic/4342/4342487.xml?temp=.4390222
      

  3.   

    bluekitty的意思是说,你什么时候调用的DeleteCriticalSection()?如果DeleteCriticalSection之后又有某个线程对该CriticalSection进行EnterCriticalSection(),那么结果是不可预期的。说到这里你可以检查一下,InitializeCriticalSection()和DeleteCriticalSection()是否处于同一线程上下文?EnterCriticalSection()和LeaveCriticalSection()是否配对,或者说是否存在某些分支条件使得两次EnterCriticalSection()才有一次LeaveCriticalSection,或者EnterCriticalSection()之后没有在同一线程上下文中执行匹配的LeaveCriticalSection?典型的例子是在支线程中抛了异常没有在同一上下文中拦截,那么该线程蹦出去了也没有LeaveCriticalSection(),其他使用该CriticalSection的线程必然阻塞。
      

  4.   

    在程序退出的时候才调用DeleteCriticalSection,如下,在TMsgNotify.Create时InitializeCriticalSection,
    在MsgNotify.Free时MsgNotify.Free,MsgNotify不是线程对象
    initialization
      MsgNotify:=TMsgNotify.Create;
    finalization
      MsgNotify.Free;
      

  5.   

    我使用Try Finally 
    会出现EnterCriticalSection()之后
    没有LeaveCriticalSection()的情况吗
      

  6.   

    以下是包含这个临界区的类代码,
    MsgNotify对象是全局对象,只有一个实例,
    其他线程使用它的DoMessage方法来更新界面,
    所以使用了临界区对象  TMsgNotify = class(TObject)
      private
        FCS: TRTLCriticalSection;
        FMsgStr: string;
        FMsgType: TMsgType;
        FOnMessage: TDeviceMsgNotifiyEvent;
      public
        constructor Create;
        destructor Destroy; override;
        procedure DoMessage(Sender: TObject;const IDevice:IMsgNotityDevice;
                AMsgType:TMsgType;AMsgStr:string);
        property MsgStr: string read FMsgStr;
        property MsgType: TMsgType read FMsgType;
        property OnMessage: TDeviceMsgNotifiyEvent read FOnMessage write FOnMessage;
      end;
    {
    ********************************** TMsgNotify **********************************
    }
    constructor TMsgNotify.Create;
    begin
      inherited;  //初始化阻塞对象
      InitializeCriticalSection(FCS);
    end;destructor TMsgNotify.Destroy;
    begin
      //删除阻塞对象
      DeleteCriticalSection(FCS);
      inherited;
    end;procedure TMsgNotify.DoMessage(Sender: TObject;const IDevice:IMsgNotityDevice;
            AMsgType:TMsgType;AMsgStr:string);
    begin
      //开始阻塞
      EnterCriticalSection(FCS);
      try
        FMsgType:=AMsgType;
        FMsgStr:=AMsgStr;
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);
      finally
        //解除阻塞
        LeaveCriticalSection(FCS);
      end;
    end;initialization
      MsgNotify:=TMsgNotify.Create;
    finalization
      MsgNotify.Free;
      

  7.   

    //开始阻塞
      EnterCriticalSection(FCS);
      try
        FMsgType:=AMsgType;
        FMsgStr:=AMsgStr;
        LeaveCriticalSection(FCS);  // 应该这里也加上吧
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);
      finally
        //解除阻塞
        LeaveCriticalSection(FCS);
      end;
      

  8.   

    楼上的这样做可是两次LeaveCriticalSection()啊。楼主的代码没有考虑这样一个问题,虽然finally块中一定会得到执行,不管有没有异常,但是如果抛了异常,finally块执行完之后异常还是要raise的,自己看看finally关键字的定义。此时异常实际上还是没有被拦截(对于构造该线程上下文的TThread来说,不是你的TMsgNotify),此时所在线程上下文的线程还是要崩溃的。上述只是一个方面,另一个方面就是你是不是在FOnMessage里有对UI的操作?虽然说有Synchronize可以强制切到主线程完成,但是切记,千万不要在EnterCriticalSection()/LeaveCriticalSection()对内部调用Synchronize()!如果不理解,你可以自己看看TThread.Synchronize的源码,可以告诉你的结论是,这样会可能会形成“两段锁”。举个例子,如果线程A进入临界区,在FOnMessage里Synchonize,此时A上下文挂起,任务调度,主线程等待进入。主线程进入后首先处理Scheduled Synchonize,然后继续完成其他的事情。注意!现在一个时间片还没有用完,此时主线程还要接着跑的。接下来主线程调用了EnterCriticalSection(),但是此时CriticalSection的Owner是A线程,而A线程现在又处于挂起状态,你说接下来会发生什么情况?
      

  9.   

    我的意思就是Scarlette(Lord of Borland)后来说的索性改成这样吧://开始阻塞
      EnterCriticalSection(FCS);
      FMsgType:=AMsgType;          // 对2个变量进行赋值应该不会引起什么异常吧
      FMsgStr:=AMsgStr;            // 不需要try..finally块
      LeaveCriticalSection(FCS);
      try
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);
      finally
        // 如果这里也不需要,就把try..finally拿掉吧
      end;
      

  10.   

    to DDGG事件处理代码中必须用到前面两个变量,所以不能把LeaveCriticalSection(FCS)提前
      //开始阻塞
      EnterCriticalSection(FCS);
      try
        FMsgType:=AMsgType;
        FMsgStr:=AMsgStr;
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);  //事件处理代码中必须用到前面两个变量
      finally
        //解除阻塞
        LeaveCriticalSection(FCS);
      end;
      

  11.   

    To Scarlette(Lord of Borland1.线程中对调用此方法的部分有异常处理
    2.FOnMessage里有对UI的操作,但没有使用Synchronize
      

  12.   

    TO: luyuhai(大海)事件处理代码中必须用到前面两个变量,如果是这种情况,应该在EnterCriticalSection()和LeaveCriticalSection()之间把这两个变量复制一份,而进入FOnMessage()后使用这两个变量的副本,这样FOnMessage()就不用放在临界区里了。//开始阻塞
      EnterCriticalSection(FCS);
      FMsgType:=AMsgType;          // 对2个变量进行赋值应该不会引起什么异常吧
      FMsgStr:=AMsgStr;            // 不需要try..finally块
      FMsgType2 := FMsgType;
      FMsgStr2 := FMsgStr;
      LeaveCriticalSection(FCS);
      try
        if Assigned(FOnMessage) then
          FOnMessage(Sender,IDevice);
      finally
        // 如果这里也不需要,就把try..finally拿掉吧
      end;.
    .procedure OnMessage(Sender, IDevice: Integer); // 假设FOnMessage所指向的函数在这里
    begin
      //OnMessage里面使用的是FMsgType2、FMsgStr2,而外面使用的是FMsgType、FMsgStr,不冲突
    end;
      

  13.   

    To DDGG(叮叮当当) 
      
      你的修改部分等于没有修改。
      事件处理中引用的变量别的线程还是会修改
      

  14.   

    好好看看Scarlette(Lord of Borland)说的!
      

  15.   

    >>此时主线程要进入临界区则程序会挂起,主线程 一般用检测,如果不能进入,就执行下一步,如用个 List 来缓冲数据,
    用个timer来定时检查,一般在主线程交互,我觉得用信号量更好,
    但有个原则,一般,主线程(UI) 不能等待的
      

  16.   

    >>2.FOnMessage里有对UI的操作,但没有使用Synchronize这里可能是个大问题吧。VCL Framework并不是线程安全的(现有的Framework也没有哪个是),所以Borland才提供TThread.Synchronize。但是Borland提供的方法并不使用于比较复杂的临界区情况。对于这种在CriticalSection内需要对UI操作的,一般都把它异步化,就是往窗体PostMessage,自己写个Message Handler来处理在线程中Post过来的自定义消息。这种做法是完全依赖与Windows消息循环的异步性的(而且Windows的消息循环是保证线程安全的),换了Linux在X环境下就不用想了(X在线程安全和异步方面真的很烂,当初可真是折腾死偶了)。如果这样还会发生主线程Deadlock的情况,我想你最好还是好好检查一下你的异步构架里有没有设计缺陷(我们平时动辄上百个线程是很常见的,都没有发生这种事)。
      

  17.   

    if Assigned(FOnMessage) then
    FOnMessage(Sender,IDevice);
    如果你的FOnMessage是执行主线程代码,那么
    就造成死锁了。
      

  18.   

    To SydPink(Miss Syd.Barrett) 
      FOnMessage中没有执行主线程代码,FOnMessage主要是更新界面,显示一些信息,并把信息保存到SqLServer数据库
      

  19.   

    to:SydPink(Miss Syd.Barrett)
    if Assigned(FOnMessage) then
    FOnMessage(Sender,IDevice);
    如果你的FOnMessage是执行主线程代码,那么
    就造成死锁了。这里为什么会死锁?我看到参考书中的例子也是不在临街区里运行主线程代码,但没有明确说临街区不能运行主线程代码,我的程序里在临街区直接对ui操作并没有出现异常。
      

  20.   

    楼上的,参考书毕竟只是“参考”,呵呵,其实理论上说在临界区里运行主线程代码也是可以的,但实际情况中你的Framework未必就线程安全。其实参考书里的不明确说也算是一种“避重就轻”。:)前面我说的在FOnMessage里用PostMessage,很大程度上也是把一个同步操作异步化,让UI动作真正的Schedule到主线程里去做,这是对现有framework线程不安全的work-around,也不是真正彻底根除问题。aiirii说的其实也是同一个意思,只不过他的做法里用List来代替Windows自身的消息循环罢了,不过,我是不太赞成用Timer的。:)如果想要真正的根除问题,那么……hoho,重新写一套framework估计是免不了的了……换句话说,每个UI对象自身也需要一个CriticalSection,以保证一个更新动作不会被一个以上的线程由于调度而“打断”重入。
      

  21.   

    呃……刚才注意到一个问题,楼主在FOnMessage里还有对SQLServer的操作?你能保证对SQLServer的动作就一定是线程安全的吗?
      

  22.   

    使用ADO访问SQlServer是线程安全的,帮助中明确说到
      

  23.   

    To Scarlette(Lord of Borland) 
      请问你程序中多个线程中如果都需要更新界面或写数据库,使用什么方法实现同步的呢?我程序中发现的问题不止题目一个,
    就是多个子线程共同访问临界区也有问题,经常有一个线程等待另一个线程退出临界区的情况,有时就死锁了。
    现在只好用Mutex,在waitsingleobject的参数中指定超时,否则也是死锁。
    我跟踪了程序,没有发现某个线程长时间占用FOnMessage过程执行的时间,每个FOnMessage执行的时间都不到一秒。
      

  24.   

    老大,我要昏过去啦,都说了两遍了,要动UI在FOnMessage里往你的Form上PostMessage啊!至于数据库的访问,我在数据库的Link对象上加一个CriticalSection,每个线程对数据库访问都要锁该CriticalSection的。前面都说过了啦…… -___-另外,临界区占用时间不是问题,就算占用一小时也不是死锁条件。对于CriticalSection你也可以TryEnterCriticalSection(),没必要用Mutex(很慢),不过TryEnterCriticalSection()只有NT族的核心支持,9x核心是不行的。我看你还是好好检查你的异步构架吧,感觉还是你构架设计上有缺陷啊。
      

  25.   

    To Lucker
      子程序更新界面并没有使用主线程,仍然是在自己的线程中执行,与主线程是否挂起没有关系。
      

  26.   

    各位大哥,谢谢指点由于多线程方面的知识欠缺,还是没有解决问题,可能正如Scarlette(Lord of Borland) 所说的,构架设计上有缺陷我只好暂时放弃多线程,当然效率低些,还好我这个采集系统的设备不是很多。恳请推荐多线程同步方面的技巧和资源。结贴