本文用於**在共享記憶體中使用容器的好處,以及幾種在共享記憶體中c++模板容器的方法。
為什麼要避開普通記憶體而選擇共享記憶體,那肯定是使用共享記憶體的優勢:
l 共享記憶體可以在多程序間共享,到達程序間通訊的方式。
l 共享記憶體可以在程序的生命週期以外仍然存在。這就可以保證在短暫停止服務(服務程序coredump,更新變更)後,服務程序仍然可以繼續使用這些共享記憶體的資料。
如果這些優勢在加上c++容器模板使用方便,開發快速的優勢,無疑是雙劍合璧,成為伺服器開發的利刃。
但如果要要做到讓容器在模板中使用,最大的麻煩是什麼?就是指標。(同步當然也是乙個問題,但我這兒強調的是容器的移植)
當然一般而言,這個指標的位址一般還是指向共享記憶體的內部資料。為什麼不會出現指向各自私有資料的情況?,如果2個程序a,共享的資料裡面的乙個指標在程序a表示程序a的私有位址,在程序b裡面標識b的私有位址,這明顯是你邏輯設計有問題。
而內部指標對於我們上面提到的2個優點,其都是天敵。另外也要注意,如果資料需要多程序共享,你的資料也必須是pod的資料,如果有虛表指標,那麼也不可能實現共享。
l 多程序之間的共用的共享記憶體,位址很可能是不一樣的。當然共享記憶體的api上一般都可以建議固定起始位址,但既然是建議,那就可能不遵守,而且這需要你熟悉程序的位址空間分布,而且對於開發者和運維者,一旦使用的共享記憶體多了,使用固定位址絕對是噩夢。
//而對於解決指標這種問題最好的方法(或者說唯一的方法)就是不記錄指標,而記錄相對的偏移位址,所有的計算都根據偏移位址處理。mmap函式的第乙個引數,就是建議位址
void *mmap (void *addr,
size_t len,
intprot,
intflags,
zen_handle handle,
size_t off = 0
);//
window的api的最後乙個引數lpbaseaddress也是建議位址。
lpvoid winapi mapviewoffileex(
_in_ dword dwdesiredaccess,
_in_ dword dwfileoffsethigh,
_in_ dword dwfileoffsetlow,
_in_ size_t dwnumberofbytestomap,
_in_opt_ lpvoid lpbaseaddress
);
目前**在共享記憶體中使用模板的方法,我見到過的思路和實現大致3種,一種是定製stl的容器記憶體分配器,一種是ace提供的使用位址無關的分配方法,一種是boost的interprocess的實現,我們分開聊聊這些方法的優點和缺點。
如果早年(04年以前)在網上的論壇搜尋答案,大部分給的答案是這個,表面看這也是乙個比較好和簡單的答案,最大程度的利用stl容器現有的**。
template class list;但其實這個答案並不一定靠譜。寫乙個共享記憶體的分配器肯定不是什麼難事。難在如果我們要把容器放入共享記憶體的那幾個目的,使用stl的容器的實現。為什麼呢,還是因為指標。
首先,很多stl容器實現裡面是有大量指標的,比如list的環形佇列的prev指標和next指標,map底層紅黑樹實現的3個指標,這些在容器內部都是用真正的記憶體位址表示的。
所以說這個答案完全要看你的stl的內部實現是否有指標,如果有,那基本不可行(當然你把資料放入共享記憶體是可以的,但你無法共享和重用)。比如sgi的實現和stlport的實現。
我看到的第二個(大約2023年)方法是ace的,在《ace programmer's guide, the: practical design patterns for network and systems programming》中文名稱《ace程式設計師指南》一書中有相應的說明,ace的方法是提供了乙個位址無關化的記憶體分配器(準確說應該是控制塊ace_pi_control_block),同時提供乙個ace_based_pointer_basic模板來記錄相對位址。而ace_based_pointer_basic模板其通過過載operator t *()函式達到幾乎和指標一樣的行為(實際會呼叫addr(),得到真正的位址)。ace的實現有點意思,我們也費點力氣剖析一下。
ace的實現上,自己的記憶體分配器記錄了分配的位址空間起始位址和長度,ace_based_pointer_basic在構造的時候會根據自己的位址判斷自己需要計算的起始位址是什麼。而且在封裝上考慮比較舒服。但需要提醒的是,你需要記錄的位址必須仍然是這塊共享記憶體上的,否則……(不解釋)。
而且要說明的是ace的自有容器雖然也支援使用共享記憶體的分配器,但由於ace容器的內部也有大量的指標,而不是記錄相對位址,所以ace的容器其實也不能在共享記憶體中。所以ace的學術氣息更加濃烈一點,實用性並不高(ace的容器本身也不好用)。所以可以說,ace踹開了那扇門,但並沒有進入這個殿堂。
這幾年boost開始流行,boost的interprocess庫中乙個在共享記憶體容器記憶體分配器的實現,但要注意其配合使用容器vector,list,是boost自己的container容器。這個分配器並不能和現有大部分stl實現配合使用。
可以說其實boost的實現和ace的思路是類似的,方法也是分配乙個塊共享記憶體,為這塊共享記憶體生成乙個容器記憶體分配器,這個分配器為這個容器服務,使用共享記憶體容器分配器後,容器內部所有的位址記錄相對位址,而不是絕對位址。
template對比ace,boost實現的不同,一方面boost的共享記憶體管理和容器記憶體分配器的思路很清晰,整體設計思路還是在stl的體系之下,ace誕生的年代過早,容器整體體系和stl完全不相容,另外一方面,boost在相對位址的處理上也簡單一點。他只記錄offset_ptr物件的this位址到需要記錄的位址之間的長度。class offset_ptr
另外,boost**雖然條理上和stl容器一致,但boost的**閱讀難度至少double於stlport,傳統除錯跟蹤**的方式單步跟蹤雖然有效,但是很多變數都無法查詢到實際值,巨集滿天飛。期待剖析boost**的大神出現。
而且另外乙個小問題是,我們申請的共享記憶體的大小都是有乙個大小限度的,而stl容器往往有隨需增長的特點,而這個特點和共享記憶體其實也有一些不調和性。
ace的問題在這個問題上給出過一些學術解,依靠訊號,異常等方式,給你機會自己擴充套件記憶體,但估計也就限於學術**範圍。
boost在這個問題上更加明了,當記憶體不夠分配的時候丟擲bad_alloc異常。反而更加清晰一點。
boost的實現固然不錯,但也有幾個並不那麼完美的地方,而且當時可以參考的思路還沒有這樣多,(我自己實現自己容器的時候是2023年,boost的interprocess的庫在08年才出現)
第一,放入n個t型別的資料的容器到底需要多大共享記憶體?因為容器本身是有消耗,而這點boost並沒有介面告訴我。對於使用共享記憶體的容器,我們都知道我們需要使用的最大數量是多少。
第二,如果最大的尺寸我們已經知道。那麼其實我們對於所有的可以在一開始就分配好空間,而不是在每次push_back的時候呼叫alloctor去分配位址,其實alloctor內部仍然使用了紅黑樹去管理所有的分配位址,坦白說麻煩。而且由於最大尺寸固定,我們所有的資料的內部位置關係都可以採用陣列下標定位。這樣也就一樣省去了指標的麻煩。
綜合上面的考慮。我們當時的設計思路大致是,根據你傳入的引數判定告知呼叫者所需的記憶體大小,呼叫者自己分配好記憶體(可以是共享記憶體),根據分配的記憶體位址構造乙個容器,容器的操作和模板基本一致,也提供迭代器等方法等方法訪問,容器的內部結構如下圖。
這個方法和stl容器的語法基本相容,效能比boost的那個速度應該要快一點(不用在每次都alloc乙個node節點)。寸有所長,尺有所短,這也算乙個思路把。
具體的**以後估計會開源。
在共享記憶體中使用模板容器的關鍵問題是指標的問題,相對位址是解決這個問題比較好的方法。乙個比較通用的方案是將所有的指標改成乙個相對位址記錄,還有一種思路對於容器的處理方式是將容器的所有資料按最大數量分配好,使用下標處理。
《stl原始碼剖析》 侯捷
《ace程式設計師指南》 馬維達
《boost documentation》
共享記憶體的使用
http download1.csdn.net down3 20070529 29183222619.chttp download1.csdn.net down3 20070529 29183246962.chttp download1.csdn.net down3 20070529 2918324...
共享記憶體的使用
共享記憶體mmap使用 date tue apr 8 14 53 43 cst 2014 include extern c using namespace std define sem file sem lock int main int argc,char argv 開啟訊號量 int count...
C 中容器的使用
c 中有兩種型別的容器 順序容器 和關聯容器 順序容器主要有vector list deque等。其中vector表示一段連續的記憶體,基於陣列實現,list表示非連續的記憶體,基於鍊錶實現,deque與vector類似,但是對首元素提供插入和刪除的雙向支援。關聯容器主要有map和set。map是k...