为什么单线程 FLTK 应用程序会出现死锁?

发布于 2024-09-12 20:42:23 字数 2627 浏览 2 评论 0原文

我有一个所谓的单线程 FLTK 应用程序,带有一个弹出菜单,是用 Fluid 创建的。我有一个类,它是 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.

}

I have a supposedly single-threaded FLTK application with a popup menu, created with Fluid. I have a class that sub-classes Fl_Gl_Window and implements a handle() method. The handle() method calls a function that creates a popup window on right-click. I have a long operation that I do for one of the menu items. My application has created a second thread for some other purpose. I use locks to protect some critical sections between my main thread and that second thread. In particular, doLongOperation() uses the locks.

My problem is that I can popup the menu twice and run doLongOperation() twice, and it then deadlocks with itself, hanging the application. Why doesn't the first doLongOperation() stall the GUI and prevent me from starting doLongOperation() a second time?

I can avoid the problem with a flag that I use to disable the offending menu item, but I would like to understand why it is possible in the first place.

Here's the code, abbreviated of course. Hopefully I've included all the relevant bits.

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.

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

温柔嚣张 2024-09-19 20:42:23

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

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

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

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

Make sure that you are not calling Fl::wait(...) from more than one thread. Am I right in inferring from your code that run() executes in its own thread?

A first call to Fl::wait(), e.g. from the main thread, would catch and process the first right-click (blocking, as expected, while the first call to doLongOperation() proceeds); in the meantime, a second thread's calls to e.g. Fl::wait(timeout)/Fl::check() would continue to refresh the display -- and will intercept (and service) the second right-click, calling handle() (in the second thread) while the first long operation is still trudging along. This would give the appearance of a deadlock, although I expect that the UI would resume redrawing (through the second thread) when both long operations complete.

Validate the above by logging the current thread ID inside popup_menu().

You should pick a single thread to call Fl::wait(...) in a loop, and you should not block that loop -- spawn any non-modal or non-UI tasks as separate threads. I.e. when popup_menu() gets called, kick off the long operation in its own thread; if popup_menu() is triggered (again) while the long operation thread is still running, either mark the popup menu item as disabled (analogous to your workaround), or simply signal the long operation thread to restart with a new parameter.

蛮可爱 2024-09-19 20:42:23

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

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

By any chance, does your doLongOperation do anything that might need the message pump (or APCs, some windows' file API uses these underneath) to be running (assuming you see this behavior on Windows)? For example, if the doLongOperation tries to update the GUI that uses SendMessage underneath, you will get the deadlock even in a single-threaded scenario.

Also, does another thread have the critical section already claimed? You should be able to break in the debugger during the hang and hopefully see who is waiting on what.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文