2013-12-20 78 views
2

我有一個具有受保護構造函數的基類IRunnable,因此不能創建基類的任何實例。該類有一個純虛擬方法run(),它必須由派生類實現。我將IRunnable *的指針傳遞給某種類似java的Executor,該Executor控制多個線程,並將這些Runnables分配給它們。 我的問題是,當這樣一個IRunnable *指針指向IRunnable派生類的一個對象,它位於另一個線程的堆棧上時,它被分配給一個工作線程,我無法確定,派生的對象是在工作線程仍在使用它時不會被銷燬,因爲線程上具有該對象的線程可能會離開創建該對象的範圍。 例如:防止派生類被破壞

int main(void) 
{ 
    CExecutor myExecutor(...); 
    for (UInt32 i = 0; i < 2; i++) 
    { 
     derivedRunnable myRunnable1; //will be destroyed after each loop iteration 
     myExecutor.submit(myRunnable1); 
    } 
} 

class derivedRunnable : public IRunnable 
{ 
public: 
    derivedRunnable(const char * name = NULL) : IRunnable(name) {} 
    ~derivedRunnable() {} 
    void run(void) 
    { 
     for (UInt32 i = 0; i < 100; i++) 
     { 
      char name [256] = {"\0"}; 

      pthread_getname_np(pthread_self(), name, 255); 
      printf("%s in step %d\n", name, i); 
     } 
     fflush(stdout); 
    } 
}; 

我實現了基類IRunnable引用計數,而我做的析構函數阻塞調用的最後一個線程使用它與它註銷時纔會回來。問題是,首先調用派生類get的析構函數,因此在調用阻塞調用的基類被破壞之前,對象將被部分地破壞。 在上面的例子中,我得到以下運行時錯誤:
pure virtual method called
terminate called without an active exception 如果我的.submit()調用後插入一些微秒的usleep,它會工作,因爲線程將可運行完成,在它被破壞之前

class IRunnable 
{ 
    friend class CThread; 
    friend class CExecutor; 
private: 
    CMutex mutx; 
    CBinarySemaphore sem; 
    UInt32 userCount; 
    [...] 
    virtual void run(void) = 0; 
    IRunnable(const IRunnable & rhs); //deny 
    IRunnable & operator= (const IRunnable & rhs); //deny 

    void registerUser(void) 
    { 
     mutx.take(true); 
     if (0 == userCount++) 
      sem.take(true); 
     mutx.give(); 
    } 

    void unregisterUser(void) 
    { 
     mutx.take(true); 
     if (0 == --userCount) 
      sem.give(); 
     mutx.give(); 
    } 

protected: 
    IRunnable(const char * n = NULL) 
    :mutx(true,false) 
    ,sem(true,false) 
    ,userCount(0) 
    { 
     setName(n); 
    } 

    ~IRunnable() 
    { 
     sem.take(true); 
    } 
    [...] 

我該怎麼辦?

回答

2

如果你在析構函數中阻塞,那麼你的提交循環將不會是異步的,這是多線程的要點。生命期必須超過分配範圍。爲此,您應該動態分配IRunnable對象,並讓您的Executor獲得所有權。執行者將負責在作業完成時將其刪除。

更好的是,爲這個接口使用共享指針。這樣調用線程仍然可以引用該對象(例如檢查完成)。最後一個線程完成後將刪除該對象。

編輯:添加代碼示例:

for (UInt32 i = 0; i < 2; i++) 
{ 
    shared_ptr<derivedRunnable> myRunnable1(new derivedRunnable); 
    myExecutor.submit(myRunnable1); 
} 

或保存任務:

list < shared_ptr <derivedRunnable> > jobs; 
for (UInt32 i = 0; i < 2; i++) 
{ 
    shared_ptr<derivedRunnable> myRunnable(new derivedRunnable); 
    myExecutor.submit(myRunnable); 
    jobs.push_back(myRunnable); 
} 
// do other stuff 
// call a status function that you haven't written yet: 
jobs.front()->WaitForCompletion(); 

順便說一句,你也應該考慮改用要麼標準::線程或升壓::線程,取決於你的編譯器的年份。

+0

我知道在堆中分配的對象不會被銷燬,直到調用一個明確的刪除,但這並不能解決我的問題。 共享指針是什麼意思? – user2950911

+0

爲什麼它不能解決你的問題?該對象在工作完成之前不會被刪除? – Peter

+0

好吧,我寧願有一個解決方案,不依賴於程序員只使用動態分配,我希望它也可以使用堆棧中的對象。你當然是對的,我的阻塞析構函數會使給定的例子同步,但對我來說這完全沒問題,這個例子只是爲了顯示發生錯誤的情況,而不是顯示一個案例如何正確使用執行器 – user2950911

1

IRunnable需要有一個虛擬析構函數,以便正確的順序調用正確的析構函數。

+0

儘管我實際上並不是100%確定虛擬析構函數的效果是什麼,但我試過了,問題仍然存在 – user2950911

+0

這個問題回答了爲什麼。具體來說,爲什麼你的代碼仍然崩潰,我不知道。我瀏覽了一下,注意到在你從其他類中派生的類中沒有虛擬解構器。 http://stackoverflow.com/questions/461203/when-to-use-virtual-destructors?lq=1 – thedaver64

+0

從你的鏈接我發現一條評論,說'虛擬'確保銷燬鏈從頂部開始的底部。我認爲這會解決我的問題。我添加了一個printf做基類和派生類的析構函數,看他們被調用的順序,但不管是否虛擬,順序總是1.派生2.基地 - 我做錯了嗎? – user2950911

2

您的問題是,它不清楚對象的所有權。在你的例子中,似乎很清楚該對象屬於線程,所以你應該通過所有權,線程應該銷燬它。因此,IRunnable析構函數必須是公開的。其實沒有理由不公開。

啊!和對象應該被動態地創建:

derivedRunnable *myRunnable1 = new derivedRunnable(); 
myExecutor.submit(myRunnable1); 

submit功能,當然接收指針並將其傳遞給該線程。當線程完成可運行時,它將破壞它:

void threadfunc(IRunnable *runnable) 
{ 
    //run it 
    delete runnable; 
} 

還有很多更復雜的解決方案,但這是最簡單的。我最喜歡的,如果C++ 11是一個選項,則使用std::unique_ptr<IRunnable>。這樣對象的銷燬就是自動的。或者,如果您需要在主線程中保持可運行狀態,則可以使用std::shard_ptr<IRunnable>:自動銷燬和共享所有權。