2009-02-17 107 views
0

我目前正在重新設計一個從輸入數據構建模型並將數據顯示給用戶的應用程序。當前系統有一個用於構建模型的線程,一個用於構建模型可視化的線程和一個顯示可視化的線程。我遇到的問題是指針在建模和可視化線程之間傳遞 - 爲了使線程安全,模型中的所有對象都必須具有互斥鎖。這意味着系統中有成千上萬的互斥鎖,兩個線程爭奪資源時都會有大量的失速。如何在模型和視圖之間共享數據?

因此,考慮到這三個線程的存在,建模和可視化線程之間以有效和線程安全的方式共享數據的最佳方式是什麼?一些數據結構很大,並且會改變建模線程的每個週期,所以我有點不願意每過一遍就製作一份數據。

編輯:

通過我們希望該系統的總延遲是有100ms的〜從接收消息,以表示顯示的變化的顯示。如果可能的話,我們希望它更快,但更重要的是我們需要它保持一致 - 現在由於互斥性爭用,我們發現週期時間有巨大的變化。從建模到可視化的數據以2D高度圖爲主 - 約18000個單元的數據。雖然模型更新中更新的實際細胞數量明顯較少 - 可能只有幾百個。

+0

對於一個有意義的答案,您必須至少給出一些數字來表示可視化的更新率,複雜度,必須複製的數據大小等等。否則,這將是我們的純粹猜測... – mghie 2009-02-17 16:53:13

回答

2

我是消息發佈/消息泵體系結構的忠實粉絲。這是MFC/Win32提供的在線程之間傳遞數據的主要方法。這種體系結構是事件驅動的線程消息,所以當接收線程正在處理線程消息時,它正在處理爲線程間通信明確提出的數據(參見下面的示例)。

您可以自己實現這一點,並將鎖定本地化爲每個線程的單個線程消息列表。所以,當一個線程需要消息的另一個線程你做大致有以下

PostThreadMessage(msg, void* param, int paramSize) 
{ 
    lock(msgQueueLock); 

    // may wish to copy param 
    char paramCpy = malloc 
    msgQueue.Queue(msg, pparam, paramSize); 

    unlock(msgQueueLock); 
} 

那麼任何線程的主循環只是

// thread's msg pump 
while (1) 
{ 
    // can also use condition var to wait for queue to change... 
    lock(msgQueueLock); 
    HandleMsgLocally(msgQueue.Deque()) 
    unlock(msgQueueLock); 
} 

反正又回到了MVC,如果事情模型的變化,它可以發佈到您的視圖來更新特定的字段,如:

// Well known msg name 
int msgName = MODEL_FIELD_A_UPDATED 

... 

void Model::UpdateFieldA(int newVal) 
{ 
    int* valToCommunicate = new int(newVal) 
    PostThreadMessage(MODEL_FIELD_A_UPDATED, valToCommunicate, sizeof(int)) 
} 


... 
void HandleMsgLocally(...void * param,) 
{ 
    if (msg == MODEL_FIELD_A_UPDATED) 
    { 
     int* val = reinterpret_cast<int*>(param); 
     //... process param 
     delete val; 
    } 
} 

優點是你本地化你的鎖定。這是巨大的。另外,只要param被髮送者明確地識別並被接收者刪除,您就不必擔心訪問共享內存。

這有很多缺點,等待時間是一個。如果您需要馬上知道某些事情發生了變化,那麼您需要真正製作共享數據並考慮最佳鎖定方案。如果你確實需要一個可以從多個方向隨時更新的全局狀態,那麼在我的書中這種情況下單例就沒問題了。這種設計實際上大多數適用於朝一個方向發展的數據,您可以進入競爭狀態。但是,您也可以實現鎖定獲取器,以便通過一個線程從另一個線程進行檢查,但要設置必須發佈的值。有很多變數需要考慮,但希望這可以幫助你。另外,你可以忘記刪除消息中的參數。

更新基於編輯 根據您的信息,根據數據量,發佈可能會很好地工作。分析你的代碼很重要。如果我是你,我會玩一些概念證明,然後切換到使用條件變量來管理同步。如果你在Windows平臺上,一定要使用他們的消息泵。

2

我已經談到任何有意義的方式回答這個問題,而不需要您更詳細的數據的難度,但這裏有反正兩個小技巧:

  • 不要超過必要的經常更新的可視化。根據可視化的複雜程度,您應該將顯示限制爲每秒3或5次更新,因此如果建模線程進展得更快,則不要顯示每次迭代。您可以讓可視化線程每x毫秒請求新數據,也可以讓建模線程在完成後請求一個新的可視化,並且自上次可視化以來已經過了足夠的時間。

  • 如果可以避免使用鎖定,請勿使用鎖定。共享指針用於不可變數據消除了大量線程爭用,因爲您不需要鎖定對象(所有訪問都是隻讀的)。通過細粒度的類設計,您還可以限制將數據複製到從一個建模循環切換到另一個建模循環的那些部分。然而,這可能需要對當前設計進行很多更改。不過,我發現這是非常值得的。

編輯:您的編輯後,我會更建議以消除鎖定儘可能多的,因爲你要儘可能快地顯示了大量的數據,但只改變也許5%的總數據。

如果您修改算法從修改單元格到基於修改後的數據創建新單元格,並且完全不修改模型,但只通過將智能指針複製到未修改的單元格並創建新模型並創建剩下的新的單元格對象,那麼你就不需要在幾千個對象中的任何一個上鎖定任何鎖。完成的模型可以傳遞給可視化線程,並且可以立即創建新模型。對於可視化線程和從模型創建的對象也是如此 - 它可以傳遞給GUI線程,並從當前模型創建一個新對象。一些細胞將成爲幾個模型的一部分,一些細胞將只是一個模型的一部分。用於創建可視化和渲染到輸出顯示的對象也可以共享這些單元。智能指針將確保單元在最後一次引用被刪除時被正確釋放 - 無論發生哪種線程。

將在程序中留下的唯一鎖定是每個線程同一個頂級鎖定訪問當前模型(或其他類似的頂級對象)。由於執行的操作將很短,所以延遲不應該成爲問題。除此之外,此設計將最大限度地利用多個處理器或內核,但需要花費更多內存和更多CPU週期。然而,這是使軟件在當前和未來硬件上表現更好的最佳方式,而這種硬件正變得越來越平行。