2010-10-11 33 views
3

在多線程環境中使用和共享的對象的列表中返回迭代器是否是一個好主意?在多線程環境中返回一個迭代器,一個好主意?

class RequestList 
{ 
public: 
    RequestList::RequestList(); 
    RequestList::~RequestList(); 

    std::list<boost::shared_ptr<Request> >::iterator GetIterator(); 
    int ListSize(); 
    void AddItem(boost::shared_ptr<Request> request); 
    void RemoveItem(boost::shared_ptr<Request> request); 
    std::list<boost::shared_ptr<Request> > GetRequestsList(); 
    boost::shared_ptr<Request> GetRequest(); 

private: 
    std::list<boost::shared_ptr<Request> > requests; 
    std::list<boost::shared_ptr<Request> >::iterator iter; //Iterator 
    boost::mutex listmtx; 
}; 




std::list<boost::shared_ptr<Request> >::iterator RequestList::GetIterator() 
{ 
    return this->iter; 
} 

用途:

RequestList* requests; 

在某些線程(可能會再次在其他線程使用)

std::list<boost::shared_ptr<Request> >::iterator iter = requests->GetIterator(); 

或者,它會更聰明,只是爲每個時間列表中的迭代器並在每個線程中使用它本地?

+4

是修改的列表嗎? – Nikko 2010-10-11 12:14:09

+0

在該特定線程中,每次都會刪除頂層項目... – 2010-10-11 12:21:17

+0

您的示例代碼看起來很奇怪。如果你能解釋爲什麼你的類需要一個迭代器成員,你的問題可能是更合適的答案。 – 2010-10-11 12:39:29

回答

2

取決於如何使用列表,但從您顯示它看起來錯誤。如果所引用的元素被刪除,迭代器將變爲無效:在這種情況下,該元素是列表中的shared_ptr對象。

只要你釋放互斥量,我猜其他一些線程可能會出現並刪除該元素。你沒有顯示這樣做的代碼,但是如果可能發生,那麼迭代器不應該「逃避」這個互斥體。

我認爲這是一個「自同步」容器,因爲互斥鎖是私有的,並且API中沒有任何東西可以鎖定它。這類事情的根本困難在於,從外部對它們進行任何迭代都不是線程安全的。這是很容易提供一個線程安全的隊列,支持:

  • 添加元素,
  • 按值移除元素,
  • 去除頭並返回其值的副本。

除此之外,很難提供有用的基本操作,因爲幾乎所有以任何有趣的方式操作列表的東西都需要完全在鎖之下完成。

通過外觀的東西,你可以複製列表GetRequestsList,並迭代副本。不知道它是否會對你有好處,因爲副本會立即過時。

+0

偉大的思想認爲相似... – 2010-10-11 12:23:21

3

不,在跨線程共享迭代器通常不是一個好主意。有幾種方法可以確保您不會遇到麻煩。

首先,迭代器是一個輕量級的對象,它構建起來很快,佔用的內存很少。所以你不應該關心任何性能問題。只要你需要創建一個實例。

這就是說你必須確保你的列表在迭代時不會被改變。我發現你班上有一個boost::mutex。鎖定這將是完美的,以確保您在迭代時不會遇到任何問題。

處理這些情況的另一種同樣有效的方法是簡單地複製內部列表並對其進行迭代。如果您需要不斷更新列表並且不希望其他線程在等待,這是一個很好的解決方案。當然,它會佔用更多的內存,但是由於您正在存儲智能指針,因此幾乎不會有任何問題。

1

通過多個線程中的迭代器訪問列表,其中主列表本身未被鎖定是危險的。

當你在不同線程中使用迭代器時,不能保證列表的狀態是什麼(例如,一個線程可以愉快地遍歷和擦除所有項目,另一個線程也會迭代,請參閱?)

如果您要在多個線程的列表中工作,請首先鎖定整個列表,然後執行您所需的操作。複製列表是一個選項,但不是最優的(取決於列表的大小和更新的速度)。如果鎖定變成瓶頸,重新考慮你的體系結構(例如,每個線程的列表?)

0

每個調用GetIterator函數的線程都會在列表中獲取它自己的存儲迭代器副本。 由於std::list<>::iterator是一個雙向迭代器,因此您製作的所有副本都完全獨立於源。如果其中一個發生了變化,這個而不是會反映在任何其他迭代器中。

至於在多線程環境中使用迭代器,這與單線程環境沒有什麼不同。只要它引用的元素是容器的一部分,迭代器就保持有效。訪問/修改容器或其元素時,您只需要注意正確的同步。

+0

什麼是'這句話似乎與其他答案相矛盾...你能詳細說明嗎? – 2010-10-11 12:42:34

+0

@Tony:看起來這樣,因爲我把重點放在其他一些事情上。其他答案主要指出了與迭代器一起工作的困難,但我只是掩飾了這一點。我從我的答案中刪除了看似矛盾的部分。 – 2010-10-11 13:11:31

0

如果列表被您的某個線程修改,您可能會遇到麻煩。

但是,當然,您可以通過在修改過程中設置鎖定和旋轉鎖定來解決這個問題。但是因爲互斥體是任何高性能程序的禍害,所以也許你可以創建一個列表(或引用)的副本,並保持原始列表互斥鎖和無鎖定?那將是最好的方法。

如果你有互斥鎖,你只需要在修改列表的同時按住迭代器的方式來解決問題,就像你通常做的那樣 - 也就是說添加元素應該沒問題,刪除應該「小心」 「但是在list上執行此操作可能不太可能像vector那樣爆炸:-)

0

我會重新考慮此設計並使用基於任務的方法。這樣你就不需要任何互斥體。 例如,使用Intel TBB,它在內部初始化任務池。所以你可以輕鬆實現一個作家/多個讀者的概念。 首先將所有請求添加到您的請求容器(一個簡單的std :: vector可能更適合於緩存局部性和性能),然後通過請求向量執行parallel_for(),但不要刪除並行請求-for()函子!
處理完成後,您實際上可以清除請求向量,而無需鎖定互斥鎖。而已!
我希望我能幫上一點忙。

相關問題