2010-10-08 48 views
2

我運行到kindof一個惱人的問題,並會需要一些建議...線程安全懶get和釋放

比方說,我有一堆小MyObject來的,可以構建更大MyExtendedObject的。 MyExtendedObject的是大和CPU佔用因此施工很懶,我儘量不盡快從內存中刪除它們儘可能:

MyExtendedObject * MyObject::GetExtentedObject(){ 
    if(NULL == ext_obj_){ 
    ext_obj_ = new MyExtendedObject; 
    } 
    ++ref_; 
    return ext_obj_; 
} 
void MyObject::ReleaseExtentedObject(){ 
    if(0 == (--ref_)) 
    { 
    if(NULL != ext_obj_) 
    { 
     delete ext_obj_; 
     ext_obj_ = NULL; 
    } 
    } 
} 

擴展對象僅在開始構建一次,當最後一個來電者釋放他們被摧毀。請注意,有些可能會被構建多次,但這不是一個問題。

現在,這絕對不是線程安全的,所以我做了一個「天真」的線程安全的實現:

MyExtendedObject * MyObject::GetExtentedObject(){ 
    Lock(); 
    if(NULL == ext_obj_){ 
    ext_obj_ = new MyExtendedObject; 
    } 
    ++ref_; 
    Unlock(); 
    return ext_obj_; 
} 
void MyObject::ReleaseExtentedObject(){ 
    Lock(); 
    if(0 == (--ref_)) 
    { 
    if(NULL != ext_obj_) 
    { 
     delete ext_obj_; 
     ext_obj_ = NULL; 
    } 
    } 
    Unlock(); 
} 

這是更好,但現在我花很多時間鎖定和解鎖一些非忽略量..

我有這種感覺,我們只有在構建或破壞時才能支付鎖定/解鎖。

我想出了這個解決方案:

MyExtendedObject * MyObject::GetExtentedObject(){ 
    long addref = InterlockedCompareExchange(&ref_, 0, 0); 
    long result; 
    do{ 
    result = addref + 2; 
    } while ((result-2) != (addref = InterlockedCompareExchange(&ref_, result, addref))); 
    if(0 == (result&1)){ 
    Lock(); 
    if(NULL == ext_obj_){ 
     ext_obj_ = new MyExtendedObject; 
     InterlockedIncrement(&ref_); 
    } 
    Unlock(); 
    } 
    return ext_obj_; 
} 
void MyObject::ReleaseExtentedObject(){ 
    long release = InterlockedCompareExchange(&ref_, 0, 0); 
    long result = 0; 
    do{ 
    result = release - 2; 
    } while ((result+2) != (release = InterlockedCompareExchange(&ref_, result, release))); 
    if(1 == result) 
    { 
    Lock(); 
    if(1 == InterlockedCompareExchange((long*)&ref_, 0, 1)) 
    { 
     if(NULL != ext_obj_) 
     { 
     delete ext_obj_; 
     ext_obj_ = NULL; 
     } 
    } 
    Unlock(); 
    } 
} 

幾點說明:

  • 我不能使用升壓。我想但實在不行。

  • 我只使用CompareExchange和Incr/Decr。不要問。

  • 我使用ref_的第一個位來存儲構造狀態(構造/未構造)和其他位來進行參考計數。這是我發現通過原子操作同時管理2個變量(參考計數和施工狀態)的唯一途徑。

現在的一些問題:

  • 你認爲這是100%的防彈?

  • 你知道一些更好的解決方案嗎?

編輯:有人建議使用shared_ptr。一個與shared_ptr工作解決方案!請注意,我需要:懶惰建設和破壞時,沒有人不再使用它。

+0

沒有必要將'Extended'拼寫爲'Extented'或'Extanted',更不用說在同一個程序中。 – 2010-10-08 15:13:24

+0

謝謝,我試圖糾正它。英語不是我的母語:) – Julio 2010-10-08 15:27:18

回答

1

正如史蒂夫說的,你基本上想要shared_ptr的建設/銷燬部分。如果你不能使用boost,那麼我建議從boost頭文件中複製適當的代碼(我相信許可證允許這樣做),或者你需要的其他解決方法來繞過你愚蠢的公司策略。這種方法的另一個優點是,當你可以採用TR1或C++ 0x時,你不需要重寫/維護任何定製的實現,你可以使用[然後]內置的庫代碼。

至於線程安全性(史蒂夫沒有提到),我發現使用同步原語幾乎總是一個好主意,而不是試圖通過自定義鎖定來讓它自己正確。我建議使用CRITICAL_SECTION,然後添加一些時間碼以確保總鎖定/解鎖時間可以忽略不計。只要沒有太多的爭用,就可以進行大量的鎖定/解鎖操作,而且您不必調試模糊的線程訪問問題。

這是我的建議,無論如何,FWIW。

編輯:我應該補充說,一旦你有效地使用boost,你可能會想要在MyObject類中保留一個weak_ptr,所以你可以檢查擴展對象是否仍然存在於「get」對它的引用。當沒有外部調用者仍在使用實例時,這將允許你的「ref counting destruction」。所以,你的「獲取」功能看起來像:

shared_ptr<MyExtendedObject> MyObject::GetExtentedObject(){ 
    RIIALock lock(my_CCriticalSection_instance); 
    shared_ptr<MyExtendedObject> spObject = my_weak_ptr.lock(); 
    if (spObject) { return spObject; } 

    shared_ptr<MyExtendedObject> spObject = make_shared<MyExtendedObject>(); 
    my_weak_ptr = spObject; 
    return spObject; 
} 

...你並不需要一個釋放功能,導致該部分通過的shared_ptr的引用計數自動完成。希望這是明確的。

請參閱:Boost weak_ptr's in a multi-threaded program to implement a resource pool瞭解有關weak_ptr和線程安全性的更多信息。

+0

感謝您的回答!但正如我所說,我仍在等待shared_ptr完全滿足我需求的解決方案。並且爲了您的信息,我的鎖解鎖機制通過關鍵部分實現。 – Julio 2010-10-08 16:38:41

+0

對於您的編輯,確定如此,它應該可以工作,並且我在早期階段就已經接近這個解決方案了:對於每個GetExtentedObject調用,您仍然擁有完全鎖定。我的最終目的是在更新引用計數時完全擺脫鎖定。 – Julio 2010-10-08 17:01:49

+0

我想看到這個證明與時間輸出/等。在我沒有使用它之前。你可以自由地嘗試進一步優化和/或嘗試推出你自己的無鎖風格實現(正如我所說,我不會推薦),但是如果你的鎖定時間與對象初始化相比是不可忽略的和其他操作,也許你有其他設計問題。 – Nick 2010-10-08 17:58:01

0

聽起來好像你正在重建boost::shared_ptr,它通過封裝的原始指針提供對象的引用計數。

在你的情況下使用將是boost::shared_ptr<MyExtendedObject>

編輯:Per @ ronag的評論,shared_ptr現在被許多當前的編譯器在被接受到最新版本的語言後本地支持。

編輯:第一次建設:

shared_ptr<MyExtendedObject> master(new MyExtendedObject); 

master最後一個副本超出範圍,delete MyExendedObject將被調用。

+0

是的,我懷疑這一點。假設我不能使用提升。我想但我真的不能。你知道,大公司,奇怪的政策等...... – Julio 2010-10-08 15:23:49

+1

@Julio - Boost的這部分內容僅包含標題。下載該標題並重新使用您需要的代碼。 – 2010-10-08 15:25:10

+2

你也有std :: tr1 :: shared_ptr或std :: shared_ptr。 – ronag 2010-10-08 16:05:29