簡體   English   中英

選擇適當的FIFO數據結構

[英]Choosing an Appropriate FIFO Data Structure

我需要一個支持索引的FIFO結構。 每個元素都是數據數組,保存在我正在讀取的設備上。 FIFO具有恆定的大小,並且在啟動時將每個元素清零。

以下是一些偽代碼可以幫助您理解此問題:

Thread A (Device Reader):
1. Lock the structure.
2. Pop oldest element off of FIFO (don't need it).
3. Read next array of data (note this is a fixed size array) from the device.
4. Push new data array onto the FIFO.
5. Unlock.

Thread B (Data Request From Caller):
1. Lock the structure.
2. Determine request type.
3. if (request = one array) memcpy over the latest array saved (LIFO).
4. else memcpy over the whole FIFO to the user as a giant array (caller uses arrays).
5. Unlock.

請注意,不應在線程B中更改FIFO,調用方應僅獲得一個副本,因此,在沒有中間副本的情況下,pop具有破壞性的數據結構不一定起作用。

我的代碼也已經具有boost依賴性,並且我在其他地方使用了無鎖的spsc_queue。 話雖如此,由於在某些情況下需要作為LIFO工作,並且有時需要遍歷整個FIFO,因此我看不到此隊列如何為我工作。

我也考慮了一個普通的std::vector ,但是當我不斷推和彈出時,我擔心性能。

問題中尚不清楚的一點是編譯器目標,即解決方案是否僅限於部分C ++ 11支持(如VS2012)或完全支持(如VS2015)。 您提到了boost依賴關系,該依賴關系為較舊的編譯器提供了類似的功能,因此我將以此為依據,並在假設boost可能提供C ++ 11之前的編譯器可能沒有的情況下,或者您可以選擇C +的情況下談論各種選項。 +11功能,例如現在標准化的互斥鎖,鎖,線程和shared_ptr。

毫無疑問,FIFO的主要工具(如您所述,有時可能需要LIFO操作)是std::deque 即使雙端隊列支持合理有效的動態存儲擴展和收縮,與您對靜態大小的基本要求相反,它的主要特征還是能夠以向量不易管理的方式同時發揮FIFO和LIFO的性能。 在內部,大多數實現都提供了一些較小的向量的集合,這些向量由雙端隊列(deque)編組,以充當單個向量容器(用於下標)的方式工作,同時允許使用有效的內存管理進行雙端推送和彈出。 使用矢量,將循環緩沖區技術用於固定大小可能很誘人,但是任何性能改進都是最小的,並且已知雙端隊列是可靠的。

您對破壞性流行音樂的觀點對我來說並不十分清楚。 那可能意味着幾件事。 std::deque提供backfront為偷看在雙端隊列兩端什么的,沒有破壞。 實際上,它們需要外觀,因為雙端隊列的pop_frontpop_back僅刪除元素,而它們不提供對彈出元素的訪問。 取出元素並彈出它是在std::deque上的兩步過程。 但是,另一種含義是,只讀請求者需要嚴格彈出來作為導航的一種方式,而不是銷毀,這實際上並不是彈出,而是遍歷。 只要結構處於鎖定狀態,就可以使用迭代器或索引輕松地對其進行管理。 或者,這也可能意味着您需要隊列的獨立副本。

假設一些表示設備數據的結構:

struct DevDat { .... };

我立即面臨這個奇怪的問題,這不是通用解決方案嗎? 出於討論的考慮,這無關緊要,但似乎意圖是特定於應用程序的操作與通用線程安全堆棧“機器”的奇怪組合,因此,我將建議一個通用解決方案,該解決方案可以容易地以其他方式進行翻譯(是,我建議使用模板類,但如果願意,您可以輕松選擇非模板)。 這些偽代碼示例很少,僅說明了容器布局的思想和建議的概念。

class SafeStackBase
{ protected: std::mutex sync;
};

template <typename Element>
class SafeStack : public SafeStackBase
{ public:  
  typedef std::deque< Element > DeQue;

  private:
  DeQue    que;

};

SafeStack可以處理堆棧中的任何類型的數據,因此細節留給Element聲明,我用typedefs進行說明:

typedef std::vector< DevDat >         DevArray;
typedef std::shared_ptr< DevArray >   DevArrayPtr;

typedef SafeStack< DevArrayPtr >      DeviceQue;

注意,我提議使用向量而不是數組,因為我不喜歡必須選擇固定大小的想法,但是顯然,std :: array是一個選擇。

SafeStackBase用於不知道用戶數據類型的代碼和數據,這就是為什么將互斥鎖存儲在其中的原因。 它很容易成為模板類的一部分,但是在非模板庫中放置非類型數據和代碼的做法有助於減少代碼膨脹(例如,不需要擴展不使用Element的功能)模板實例化)。 我建議使用DevArrayPtr,以便可以將陣列“拔出”隊列而無需復制陣列,然后在shared_ptr的共享所有權下將其共享和分發到結構外部。 這只是說明問題,不能充分處理有關這些數組內容的問題。 這可以由DevDat進行管理,DevDat可以整理數組數據的讀取,同時將數組數據的寫入限制在授權的朋友(寫訪問器策略)上,從而使線程B(僅讀者)不能粗心地修改數組數據。內容。 這樣,可以在不復制數據的情況下提供這些陣列。只需返回DevArrayPtr的副本即可對整個陣列進行公共訪問。 這還支持返回DevArrayPtr支持ThreadB點4的容器(將整個FIFO復制到用戶),如下所示:

typedef std::vector< DevArrayPtr >    QueArrayVec;
typedef std::deque< DevArrayPtr >     QueArrayDeque;
typedef std::array< DevArrayPtr, 12 > QueArrays;

關鍵是您可以返回任何您喜歡的容器,這只是一個指向內部std::array< DevDat >的指針的std::array< DevDat > ,從而通過要求一些授權對象進行寫入來讓DevDat控制讀/寫授權,並且此副本是否應QueArrayDeque可以作為FIFO操作而不會干擾線程A的寫所有權, QueArrayDeque它提供了完整的功能集,並具有獨立的FIFO / LIFO結構。

這帶來了關於線程A的觀察。您在狀態鎖中是步驟1,而在狀態鎖中是步驟5,但是我認為在鎖狀態下實際上只需要步驟2和4。 第3步可能會花費一些時間,即使您認為這是很短的時間,也不會比彈出后跟推的時間短。 關鍵是鎖定實際上是控制FIFO / LIFO隊列結構,而不是從設備讀取數據。 這樣,可以將數據格式化為DevArray,然后將其提供給SafeStack,使其在鎖定狀態下彈出/彈出。

假設SafeStack中的代碼:

typedef std::lock_guard< std::mutex >  Lock; // I use typedefs a lot

void StuffIt( const Element & e ) 
{ Lock l( sync );
  que.pop_front();
  que.push_back( e );    
}

StuffIt完成簡單,通用的工作,即在鎖定狀態下彈出前部,將后部推回。 由於它使用const Element & ,因此線程A的步驟3已經完成。 正如我所建議的那樣,由於Element是一個DevArrayPtr,因此可以將其用於:

DeviceQue  dq;

auto p = std::make_shared<DevArray>();

dq.StuffIt( p );

DevArray的填充方式取決於它的構造函數或某個函數,關鍵是使用shared_ptr來傳輸它。

這帶來了關於SafeStack的更通用的觀點。 顯然,標准訪問功能有潛力,可以模仿std :: deque,但是SafeStack的主要工作是鎖定/解鎖訪問控制,並在鎖定狀態下執行某些操作。 為此,我提出一個通用函子就足以概括該概念。 首選的機制,特別是關於增強的機制,取決於您,但是類似(SafeStack中的代碼)的東西:

bool LockedFunc( std::function< bool(DevQue &)> f )
{
 Lock l( sync );
 f( que );
}

或者您喜歡使用以DevQue作為參數來調用函子的任何機制。 這意味着您可以在處於鎖定狀態時使用對雙端隊列(及其接口)的完全訪問權限來構造回調,或者提供在鎖定狀態下執行特定任務的函子或lambda。

設計重點是使SafeStack小型化,專注於最小的任務,該任務是在鎖定狀態下完成一些事情,並處理隊列中的大多數數據。 然后,使用最后一點,在shared_ptr下提供數組,以提供線程B步驟3和4的服務。

為了清楚起見,請記住,對shared_ptr進行復制所執行的任何操作都類似於對容器進行簡單的POD類型(如int)所進行的操作。 也就是說,可以在同一代碼中遍歷DevQue的元素,從而將這些元素的副本復制到另一個容器中,這將對整數容器進行操作(請記住,這是模板的成員函數-該類型是通用的) 。 最終的工作僅是復制指針,這比復制整個數據數組要少。

現在,第4步對我來說還不是很清楚。 看來您需要返回一個DevArray,它是隊列中所有條目的累積內容。 安排起來很簡單,但是使用向量可能會更好(因為它是可動態擴展的),但是只要std :: array有足夠的空間,那肯定是可能的。

但是,此類數組與隊列的本機“數組數組”之間的唯一真正區別是如何遍歷(和計數)數組。 返回一個Element(第3步)很快,但是由於第4步是在鎖定狀態下進行的,所以這比大多數鎖定功能在不需要時真正應該做的要多。

我建議SafeStack應該能夠提供que(DeQue typedef)的副本,這是快速的。 然后,在鎖之外,線程B擁有DeQue的副本(std :: deque <DevArrayPtr>),以形成自己的“巨型數組”。

現在,有關該數組的更多信息。 到目前為止,我還沒有適當地處理編組。 我只是建議DevDat這樣做,但這可能還不夠。 當然,可以寫出傳達DevDat集合的std :: array或std :: vector的內容。 也許這應該是它自己的外部結構。 我將其留給您,因為我的意思是SafeStack現在專注於它的小任務(鎖定/訪問/解鎖),並且可以接受可被share_ptr(或POD和可復制對象)擁有的任何東西。 以相同的方式,SafeStack是一個外殼,將一個std :: deque與一個互斥體編組在一起,一些類似的外殼可以將對DevDats的std :: vector或std :: array的只讀訪問權限編組,並使用一種寫訪問器。線程A。這可能很簡單,因為它僅允許構造std :: array來創建其內容,然后可以提供只讀訪問權限。

我建議您使用boost::circular_buffer ,這是一個固定大小的容器,支持隨機訪問迭代,在開始和結束時進行恆定時間插入和擦除。 您可以將其用作帶有push_back()的FIFO,讀取back()獲取最新保存的數據,並通過begin(), end()或使用operator[]遍歷整個容器。

但是在啟動時,這些元素不會被清零。 我認為它有一個更加方便的界面。 容器最初是空的,插入將增加大小,直到達到最大大小。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM