【轉】緩衝區設計--環形隊列

來源:互聯網
上載者:User

標籤:

原文連結:http://blog.csdn.net/billow_zhang/article/details/4420789

在程式的兩個模組間進行通訊的時候,緩衝區成為一個經常使用的機制。

 

如,寫入模組將資訊寫入緩衝區中,讀出模組將資訊讀出緩衝區。這樣使得:

  • 將程式清晰地劃分模組,建立良好的模組化架構,使得寫入和讀出成為高彙總,低耦合的模組。
  • 對於寫入和讀出的處理可能產生的快慢不均勻的情況進行平衡,使得整個處理的速度趨於平滑的均勻狀態,避免出現讀出模組處理的慢速使得寫入模組等待使得響應速度下降的狀況;同樣,也避免寫入模組的快慢不均勻,使得讀出模組忙閑不一的情況。
  • 可以增加處理的並發性。由於寫入和讀出模組的良好設計和劃分,可以使得它們彼此隔離和獨立,從而,使用線程和進程產生不同的並發處理,並通過緩衝區大小的調節,使得這個處理達到良好的匹配和運行狀態。例如,寫入模組可以有N個線程或進程,讀出模組可以有M個線程和進程,緩衝沖區可以配置L的大小。N、M、L可以通過類比實驗設定適應具體應用的值。也可以建立一套自動調節的機制,當然,這樣會造成設計的複雜性。

緩衝區顯然不適合下面的情況:

  • 資料的接收處理原本就是密切相關,難以劃分模組。
  • 處理中的模組間明顯不存在處理不均勻的情況,或者不是主要問題。
  • 需要同步響應的情況。顯然,寫入端只是將資訊push到隊列中,並不能得到讀出端的處理響應資訊,只能適合於非同步資訊傳遞的情況。
緩衝區的設計:
  • 緩衝區是一個先進先出隊列。寫入模組將資訊插入隊列;讀出模組將資訊彈出隊列。
  • 寫入模組與讀出模組需要進行資訊的協調和同步。
  • 對於多線程和多進程的寫入或讀出模組,寫入模組間以及讀出模組間需要進行臨界區處理。

 

 

隊列使用環形隊列,如。環形隊列的特點是,不需要進行動態記憶體釋放和分配,使用固定大小的記憶體空間反覆使用。在實際的隊列插入和彈出操作中, 是不斷交叉進行的,當push操作時,head會增加;而pop操作時,tail會增加。push的速度快的時候,有可能追上 tail,這個時候說明隊列已經滿了,不能再進行push的操作了,需要等待 pop 操作騰出隊列的空間。當 pop 的操作快,使得 tail 追上 head,這個時候說明隊列已空了,不能再進行 pop 操作了,需要等待 push 進來資料。

 

 

下面列出了一個環形隊列類的的資料結構的來源程式 。

 

 

[cpp] view plaincopy
  1.    
  2. /* LoopQue.h 
  3.    Author: ZhangTao 
  4.    Date: July 26, 2009 
  5. */  
  6.    
  7. # ifndef LoopQue_h  
  8. # define LoopQue_h  
  9.    
  10. # include       <new>  
  11.    
  12. namespace xtl   {  
  13.    
  14. template<typename _Tp>  
  15. class LoopQue_impl  {  
  16.   public:  
  17.     static int addsize(int max_size) {  
  18.       return max_size * sizeof(_Tp);  
  19.     }  
  20.    
  21.     LoopQue_impl(int msize) : max_size(msize), _front(0), _rear(0), _size(0) {}  
  22.    
  23.     _Tp& front() { return data[_front]; }  
  24.    
  25.     void push(const _Tp& value)  {  
  26.       data[_rear] = value;  
  27.       _rear = (_rear + 1) % max_size;  
  28.       _size++;  
  29.     }  
  30.    
  31.     void pop()  {  
  32.       _front = (_front + 1) % max_size;  
  33.       _size--;  
  34.     }  
  35.    
  36.     int check_pop(_Tp& tv)  {  
  37.       if ( empty() )  
  38.         return -1;  
  39.    
  40.       tv = front();  
  41.       pop();  
  42.     }  
  43.    
  44.     int check_push(const _Tp& value)  {  
  45.       if ( full() )  
  46.         return -1;  
  47.    
  48.       push(value);  
  49.     }  
  50.    
  51.     bool full() const  { return _size == max_size; }  
  52.     bool empty() const { return _size == 0; }  
  53.     int  size() const { return _size; }  
  54.     int  capacity() const { return max_size; }  
  55.    
  56.   private:  
  57.     int32_t _front;  // front index  
  58.     int32_t _rear;   // rear index  
  59.     int32_t _size;   // queue data record number  
  60.    
  61.     const int32_t max_size; // queue capacity  
  62.    
  63.     _Tp  data[0];    // data record occupy symbol  
  64. };  
  65.    
  66. template<typename _Tp>  
  67. struct LoopQue_allocate  {  
  68.   LoopQue_impl<_Tp>& allocate(int msize)    {  
  69.     char *p = new char[sizeof(LoopQue_impl<_Tp>) +  
  70.                 LoopQue_impl<_Tp>::addsize(msize)];  
  71.     return *(new (p) LoopQue_impl<_Tp>(msize));  
  72.   }  
  73.    
  74.   void deallocate(void *p)   {  
  75.     delete [] (char *)p;  
  76.   }  
  77. };  
  78.    
  79. template< typename _Tp, typename Alloc = LoopQue_allocate<_Tp> >  
  80. class LoopQue   {  
  81.   public:  
  82.     typedef _Tp  value_type;  
  83.    
  84.     LoopQue(int msize) : impl(alloc.allocate(msize)) {}  
  85.     ~LoopQue()  { alloc.deallocate((void *)&impl); }  
  86.    
  87.     value_type& front() { return impl.front(); }  
  88.     const value_type& front() const { return impl.front; }  
  89.    
  90.     void push(const value_type& value)  { impl.push(value); }  
  91.     void pop()  { impl.pop(); }  
  92.    
  93.     int check_pop(value_type& tv)  { return impl.check_pop(tv); }  
  94.     int check_push(const value_type& value)  { return impl.check_push(value); }  
  95.    
  96.     bool full() const  { return impl.full(); }  
  97.     bool empty() const { return impl.empty(); }  
  98.     int  size() const { return impl.size(); }  
  99.    
  100.   private:  
  101.     Alloc alloc;  
  102.     LoopQue_impl<_Tp>& impl;  
  103. };  
  104.    
  105. } // end of < namespace stl >  
  106.    
  107. # endif  // end of <ifndef LoopQue_h>  
  108.    

 

 

 

程式裡定義了兩個類 LoopQue_impl及LoopQueue。前者定義了環形隊列的基本資料結構和實現,後者又進行了一次記憶體配置封裝。
21行的LoopQue_impl的建構函式是以隊列的空間大小作為參數建立這個類的。也就是說,在類建立的時候,就決定了隊列的大小。

63行中定義的隊列的數組空間為 _Tp data[0]。這似乎是一個奇怪的事情。事實上,這個空間大小應該是max_size個數組空間。 但由於max_size是在類建立的時候確定的,在這裡,data[0]只起到一個預留位置的作用。所以,LoopQue_impl這個類是不能直接使用的, 需要正確的分配好記憶體大小,才能使用,這也是需要裡另外設計一個類LoopQueue的重要原因之一。也許您會奇怪,為什麼要這樣使用呢?如果定義一個指標, 例如:_Tp *data,然後在建構函式裡面使用 data = new _Tp[max_size],不是很容易嗎?但是,不要忘記了,我們這個環形隊列類有可能會是一個進程間共用類。 例如,一個進程push操作,另一個進程pop操作。這樣,這個類是需要建立在共用記憶體中的。而共用記憶體中的類的成員,如果包含有指標或者引用這樣的類型, 將給記憶體配置帶來很大的麻煩。而我們這樣以這個預留位置的方式設計這個類,將減少這種麻煩和複雜性。

17行的addsize的類函數確定了LoopQue_impl需要的另外的記憶體空間的大小。

LoopQueue顯示了怎樣使用LoopQue_impl,解決記憶體配置問題的。從79行的模版參數中,我們看到,除了緩衝區資料存放類型_Tp的參數外,還有一個Alloc類型。 這便是用於分配LoopQue_impl記憶體空間使用的模版類。

在LoopQueue的成員中,定義了LoopQue_impl的一個引用 impl(102行)。這個引用便是指向使用Alloc分配空間得來的LoopQue_impl的空間。


Alloc模版參數有一個預設的定義值 LoopQue_allocate。從這個預設的分配記憶體的類裡,我們可以看到一個分配LoopQue_impl的實現範例,見69行:


   char *p = new char[sizeof(LoopQue_impl<_Tp>) + LoopQue_impl<_Tp>::addsize(msize)];
       return *(new (p) LoopQue_impl<_Tp>(msize));


這裡,先根據msize分配好了靠慮到了data[msize]的足夠的記憶體,然後,再使用定位的new操作,將LoopQue_impl建立在這個記憶體區中。這樣,LoopQue_impl類就可以使用它其中的 _Tp data[0]的成員。實際上,這個成員已經有 _Tp data[msize]這樣的空間了。 這裡,如果我們設計另外一個分配記憶體的類,例如,LoopQue_ShmAlloc。這個類是使用共用記憶體,並在其中建立LoopQue_impl類。這樣我們就可以使用:
LoopQue<_Tp, LoopQue_ShmAlloc>

來建立一個可以在進程間共用而進行通訊的環形隊列類了。

 

至此,我們可以總結一下:
  • 環形隊列的資料結構和基本操作都是相當簡單的。主要的操作就是push和get。
  • 環形隊列可能需要線程或者進程間共用操作。尤其是進程間的共用,使得記憶體配置成為了一個相對複雜的問題。對於這個問題,我們將基礎的資料結構定義為無指標和引用成員,適合於記憶體管理的類,然後又設計了一個代理的進行二次封裝的類,將記憶體配置的功能作為模版可定製的參數。
這裡,我們設計並實現了環形隊列的資料結構,並也為這個結構能夠定製存放到共用記憶體設計好了方針策略。但下面的工作,將更富於挑戰性:
  • 對於push的操作,需要操作前等待隊列已經有了空間,也就是說隊列沒有滿的狀態。等到這個狀態出現了,才繼續進行push的操作,否則,push操作掛起。
  • 對於get 的操作,需要操作前等待隊列有了資料,也就是說隊列不為空白的狀態。等到這個狀態出現了,才繼續進行get的操作,否則,get操作掛起。
  • 上面的push操作/get操作一般是不同的進程和線程。同時,也有可能有多個push的操作和get操作的進程和線程。

這些工作我們將在隨後的設計中進行。且聽下回分解。

 

 

附件: LoopQue的測試程式:

 

[cpp] view plaincopy
  1.    
  2. /* tst-loopque.cpp 
  3.  test program for <LoopQue> class 
  4.  Author: ZhangTao 
  5.  Date: July 27, 2009 
  6. */  
  7.    
  8. # include       <iostream>  
  9. # include       <cstdlib>       // for <atol> function  
  10. # include       "xtl/LoopQue.h"  
  11.    
  12. int  
  13. main(int argc, char **argv)  
  14. {  
  15.   int  qsize = 0;  
  16.    
  17.   if ( argc > 1 )  
  18.     qsize = atol(argv[1]);  
  19.    
  20.   if ( qsize < 1 )  
  21.     qsize = 5;  
  22.    
  23.   xtl::LoopQue<int>  queue(qsize);  
  24.   for ( int i = 0; i < (qsize - 1); i++ )       {  
  25.      queue.push(i);  
  26.      std::cout << "Loop push:" << i << "/n";  
  27.   }  
  28.    
  29.   queue.check_push(1000);  
  30.   std::cout << "Full:" << queue.full() << " Size:"  
  31.         << queue.size() << "/n/n";  
  32.    
  33.   for ( int i = 0; i < qsize; i++ )   {  
  34.     int val = queue.front();  
  35.     std::cout << "Loop pop:" << val << "/n";  
  36.     queue.pop();  
  37.   }  
  38.    
  39.   std::cout << "/nEmpty:" << queue.empty() << " Size:"  
  40.         << queue.size() << "/n";  
  41.   return 0;  
  42.    
  43. }   

 

【轉】緩衝區設計--環形隊列

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.