C++容器模板在共享内存中的使用

本文用于探讨在共享内存中使用容器的好处,以及几种在共享内存中C++模板容器的方法。

1 为什么要在共享内存中使用模板容器?

为什么要避开普通内存而选择共享内存,那肯定是使用共享内存的优势:

共享内存可以在多进程间共享,到达进程间通信的方式。

共享内存可以在进程的生命周期以外仍然存在。这就可以保证在短暂停止服务(服务进程coredump,更新变更)后,服务进程仍然可以继续使用这些共享内存的数据。

如果这些优势在加上C++容器模板使用方便,开发快速的优势,无疑是双剑合璧,成为服务器开发的利刃。

2 在共享内存中使用模板容器最大难点是?

但如果要要做到让容器在模板中使用,最大的麻烦是什么?就是指针。(同步当然也是一个问题,但我这儿强调的是容器的移植)

当然一般而言,这个指针的地址一般还是指向共享内存的内部数据。为什么不会出现指向各自私有数据的情况?,如果2个进程A,共享的数据里面的一个指针在进程A表示进程A的私有地址,在进程B里面标识B的私有地址,这明显是你逻辑设计有问题。

而内部指针对于我们上面提到的2个优点,其都是天敌。另外也要注意,如果数据需要多进程共享,你的数据也必须是POD的数据,如果有虚表指针,那么也不可能实现共享。

多进程之间的共用的共享内存,地址很可能是不一样的。当然共享内存的API上一般都可以建议固定起始地址,但既然是建议,那就可能不遵守,而且这需要你熟悉进程的地址空间分布,而且对于开发者和运维者,一旦使用的共享内存多了,使用固定地址绝对是噩梦。

而对于服务器,上一次映射的地址,有可能和重启后的映射地址不一致。

//mmap函数的第一个参数,就是建议地址 void *mmap (void *addr, size_t len, int prot, int flags, ZEN_HANDLE handle, size_t off = 0); //Window的API的最后一个参数lpBaseAddress也是建议地址。 LPVOID WINAPI MapViewOfFileEx( _In_ HANDLE hFileMappingObject, _In_ DWORD dwDesiredAccess, _In_ DWORD dwFileOffsetHigh, _In_ DWORD dwFileOffsetLow, _In_ SIZE_T dwNumberOfBytesToMap, _In_opt_ LPVOID lpBaseAddress ); 

而对于解决指针这种问题最好的方法(或者说唯一的方法)就是不记录指针,而记录相对的偏移地址,所有的计算都根据偏移地址处理。

目前探讨在共享内存中使用模板的方法,我见到过的思路和实现大致3种,一种是定制STL的容器内存分配器,一种是ACE提供的使用地址无关的分配方法,一种是BOOST的interprocess的实现,我们分开聊聊这些方法的优点和缺点。

3 定制STL的分配器

如果早年(04年以前)在网上的论坛搜索答案,大部分给的答案是这个,表面看这也是一个比较好和简单的答案,最大程度的利用STL容器现有的代码。

template <class _Tp, class _Alloc> class list;

但其实这个答案并不一定靠谱。写一个共享内存的分配器肯定不是什么难事。难在如果我们要把容器放入共享内存的那几个目的,使用STL的容器的实现。为什么呢,还是因为指针。

首先,很多STL容器实现里面是有大量指针的,比如list的环形队列的prev指针和next指针,map底层红黑树实现的3个指针,这些在容器内部都是用真正的内存地址表示的。

所以说这个答案完全要看你的STL的内部实现是否有指针,如果有,那基本不可行(当然你把数据放入共享内存是可以的,但你无法共享和重用)。比如SGI的实现和STLport的实现。

4 ACE的与位置无关的分配

我看到的第二个(大约2005年)方法是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的实现有点意思,我们也费点力气剖析一下。

image

如果上图的例子,进程A,B共享一段共享内存,分别映射在不同的地址上,共享内存中有一个结构S,S中要记录一个这段共享内存中的另外一个地址char *,结构S可以使用ACE的ACE_Based_Pointer_Basic<char> 记录这个地址,ACE_Based_Pointer_Basic<char>分别使用2个长度记录自己到共享内存起始地址的长度,以及需要记录的地址到共享内存的起始位置的地址。然后两个进程就都可以通过this指针,和2个偏移长度,计算得到需要记录的地址。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/423dfc563ea6514d8f9eb2b43007df55.html