2010-08-05 24 views
2

我有一個假想的單線程FLTK應用程序與彈出菜單,創建與流體。我有一個類Fl_Gl_Window子類,並實現一個handle()方法。 handle()方法調用一個函數,在右鍵單擊時創建一個彈出窗口。對於其中一個菜單項,我有很長的操作。我的應用程序爲其他目的創建了第二個線程。我使用鎖來保護主線程和第二線程之間的一些關鍵部分。特別是,doLongOperation()使用鎖。爲什麼我可以在單線程FLTK應用程序中死鎖?

我的問題是,我可以彈出菜單兩次,運行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

確保您不是從多個線程中調用Fl::wait(...)我是否正確地從您的代碼推斷run()在自己的線程中執行?

Fl::wait()的第一次呼叫,例如,從主線程,將捕獲並處理第一次右鍵單擊(阻塞,如預期的,而第一次調用doLongOperation()繼續);同時,第二個線程的呼叫例如Fl::wait(timeout)/Fl::check()將繼續刷新顯示 - 並且將攔截(和服務)第二次右鍵單擊,在第一個長操作仍在繼續時,呼叫handle()(在第二個線程中)。這會導致死鎖的出現,但我期望當兩個長操作完成時UI將恢復重繪(通過第二個線程)。

通過在popup_menu()中記錄當前線程ID來驗證上述內容。

您應該選擇一個線程在循環中調用Fl::wait(...),並且不應該阻止該循環 - 將任何非模態或非UI任務產生爲單獨的線程。即當popup_menu()被調用時,在其自己的線程中啓動長操作;如果在長操作線程仍在運行時觸發(再次)popup_menu(),則可以將彈出菜單項目標記爲禁用(類似於您的解決方法),或者只需用長參數重新啓動長操作線程即可。

+1

我不是從多個線程調用wait()或check(),而是在doLongOperation()內調用Fl :: check()。它在另一個功能之內,所以它不在視線之內。 Fl :: check()讓GUI處理事件,讓我開始另一個doLongOperation()。第二個人阻止等待第一個解鎖,但第一個在第二個正在運行時被阻塞。因此,你的答案並不是確切的解決方案,但你的元點(「小心wait()和check()」)是找出我出錯的地方的觸發器,所以你得到了賞金。謝謝! – cape1232 2010-08-12 13:16:56

+0

對不起1/2賞金。我認爲選擇你的答案就足以把它給你。我錯過了我應該明確地去做的地方。我的第一個賞金,加上錯誤,你爲此感到痛苦。生活對你來說太不公平了。 – cape1232 2010-08-19 03:12:53

1

以任何機會,貴doLongOperation做任何事情,可能需要消息泵(或裝甲運兵車,一些Windows的文件API使用這些下)在運行(假設你看到在Windows上的行爲)?例如,如果doLongOperation嘗試更新下面使用SendMessage的GUI,即使在單線程方案中也會遇到死鎖。

此外,另一個線程是否已經聲明瞭關鍵部分?你應該能夠在掛機期間打破調試器,並希望看到誰在等待什麼。