代码之家  ›  专栏  ›  技术社区  ›  cape1232

为什么我可以在单线程的FLTK应用程序中死锁?

  •  2
  • cape1232  · 技术社区  · 16 年前

    我有一个单线程的fltk应用程序,它有一个弹出菜单,用fluid创建。我有一个类,它子类fl_g_窗口并实现handle()方法。handle()方法调用一个函数,该函数在右键单击时创建一个弹出窗口。我对其中一个菜单项进行了长时间的操作。我的应用程序为其他目的创建了第二个线程。我使用锁来保护主线程和第二线程之间的一些关键部分。特别是,dolongoperation()使用锁。

    我的问题是,我可以弹出菜单两次,运行dolongoperation()两次,然后它会自动死锁,挂起应用程序。 为什么第一个dolongoperation()不停止GUI并阻止我第二次启动dolongoperation()?

    我可以避免使用一个标记来禁用有问题的菜单项,但我想了解为什么一开始就可以这样做。

    这是代码,当然是缩写。希望我已经包含了所有相关的部分。

    class MyClass {
      void doLongOperation();
    };
    
    class MyApplication : public MyClass {
      MyApplication();
      void run();
      void popup_menu();
    };
    
    void MyClass::doLongOperation()
    {
       this->enterCriticalSection();
       // stuff
       // EDIT
       // @vladr I did leave out a relevant bit.
       // Inside this critical section, I was calling Fl::check().
       // That let the GUI handle a new popup and dispatch a new
       // doLongOperation() which is what lead to deadlock.
       // END EDIT
       this->leaveCriticalSection();
    } 
    
    MyApplication::MyApplication() : MyClass() 
    {
      // ...
      { m_mainWindowPtr = new Fl_Double_Window(820, 935, "Title");
        m_mainWindowPtr->callback((Fl_Callback*)cb_m_mainWindowPtr, (void*)(this));
        { m_wireFrameViewPtr = new DerivedFrom_Fl_Gl_Window(10, 40, 800, 560);
          // ...
        }
        m_mainWindowPtr->end();
      } // Fl_Double_Window* m_mainWindowPtr
    
    m_wireFrameViewPtr->setInteractive();
    
    m_mainWindowPtr->position(7,54);
    m_mainWindowPtr->show(1, &(argv[0]));
    
    Fl::wait();
    }
    
    void MyApplication::run() {
      bool keepRunning = true;
      while(keepRunning) {
    
      m_wireFrameViewPtr->redraw();
      m_wireFrameView2Ptr->redraw();
    
      MyClass::Status result = this->runOneIteration();
      switch(result) {
      case DONE: keepRunning = false; break;
      case NONE: Fl::wait(0.001); break;
      case MORE: Fl::check(); break;
      default: keepRunning = false;
      }
    
    }
    
    void MyApplication::popup_menu() {
      Fl_Menu_Item *rclick_menu;
    
    
      int longOperationFlag = 0;
      // To avoid the deadlock I can set the flag when I'm "busy".
      //if (this->isBusy()) longOperationFlag = FL_MENU_INACTIVE;
    
      Fl_Menu_Item single_rclick_menu[] = {
         { "Do long operation", 0, 0, 0, longOperationFlag },
         // etc. ...
         { 0 }
      };
    
      // Define multiple_rclick_menu...
    
      if (this->m_selectedLandmarks.size() == 1) rclick_menu = single_rclick_menu;
      else rclick_menu = multiple_rclick_menu;
    
      const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0);
    
      if (!m) return;
    
    
      if (strcmp(m->label(), "Do long operation") == 0) {
        this->doLongOperation();
        return;
      }
    
      // Etc.
    
    }
    
    2 回复  |  直到 15 年前
        1
  •  2
  •   Tim Cooper    15 年前

    确保你没有 打电话 Fl::wait(...) 来自多个线程。 我从你的代码中推断出 run() 在自己的线程中执行?

    第一次呼叫 Fl::wait() 例如,从主线程捕获并处理第一个右键单击(如预期,阻塞,而第一个调用 doLongOperation() 同时,第二个线程调用 Fl::wait(timeout)/Fl::check() 将继续刷新显示--并截获(和服务)第二个右键单击,调用 handle() (在第二个线程中)当第一个长操作仍在艰难进行时。这将导致死锁的出现,尽管我希望当两个长操作都完成时,UI将恢复重绘(通过第二个线程)。

    通过将当前线程ID记录在 popup_menu() .

    你应该选择一个线程来调用 Fl:等待… 在循环中,不应阻塞该循环 --将任何非模态或非UI任务作为单独的线程生成。即当 PopUpMeNuu() 在其自身线程中启动长操作;如果 PopUpMeNuu() 当长操作线程仍在运行时被触发(再次),要么将弹出菜单项标记为禁用(类似于您的解决方法),要么简单地向长操作线程发出用新参数重新启动的信号。

        2
  •  1
  •   Chris O    16 年前

    不管怎样,你的 doLongOperation 是否有任何可能需要消息泵(或APC,一些Windows的文件API在下面使用这些消息泵)运行的操作(假设您在Windows上看到此行为)?例如,如果 多隆操作 尝试更新使用的GUI SendMessage 在下面,即使在单线程场景中,您也会得到死锁。

    另外,另一个线程是否已经声明了关键部分?您应该能够在挂起期间插入调试器,并希望看到谁在等待什么。