【问题标题】:Why can I deadlock in a single-threaded FLTK application?为什么我会在单线程 FLTK 应用程序中死锁?
【发布时间】:2011-03-25 20:47:39
【问题描述】:

我有一个使用 Fluid 创建的带有弹出菜单的单线程 FLTK 应用程序。我有一个子类 Fl_Gl_Window 并实现了 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.

}

【问题讨论】:

    标签: deadlock fltk single-threaded


    【解决方案1】:

    确保您没有从多个线程调用Fl::wait(...)我从您的代码中推断run() 在其自己的线程中执行是否正确?

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

    通过在popup_menu() 中记录当前线程 ID 来验证上述内容。

    您应该选择一个线程来循环调用Fl::wait(...),并且您不应该阻塞该循环——将任何非模态或非 UI 任务作为单独的线程生成。 IE。当popup_menu() 被调用时,在它自己的线程中启动长操作;如果在长操作线程仍在运行时(再次)触发popup_menu(),则将弹出菜单项标记为禁用(类似于您的解决方法),或者简单地向长操作线程发出信号以使用新参数重新启动。

    【讨论】:

    • 我没有从多个线程调用 wait() 或 check(),但我在 doLongOperation() 中调用了 Fl::check()。它在另一个函数中,所以它不在视线范围内,也不在脑海中。 Fl::check() 让 GUI 处理事件,这让我开始另一个 doLongOperation()。第二个阻塞等待第一个释放锁,但第一个在第二个运行时被阻塞。所以,你的答案不是确切的解决方案,但你的元点(“小心等待()和检查()”)是找出我哪里出错的触发器,所以你得到了赏金。谢谢!
    • 抱歉 1/2 赏金。我认为选择你的答案就足够给你了。我错过了我应该明确做的地方。我的第一个赏金,加上错误,你为此受苦。生活是如此不公平……对你。
    【解决方案2】:

    您的doLongOperation 是否有任何可能需要消息泵(或 APC,某些 Windows 的文件 API 在下面使用这些)才能运行的事情(假设您在 Windows 上看到此行为)?例如,如果doLongOperation 尝试更新下面使用SendMessage 的GUI,即使在单线程情况下也会出现死锁。

    另外,另一个线程是否已经声明了临界区?您应该能够在挂起期间中断调试器,并希望看到谁在等待什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多