Skip to content

STL容器放入共享内存

STL可以说是C++的重要的组成部分,它提供了一系列方便使用的容器,可以供我们存放数据,节省了很多开发时间。不过如果打算在多个进程之间共享一个STL容器,却常常会遭遇一些困难。比如你正在使用一个map,一般的IPC方法,可能下意识的你就会选择使用共享内存,如果可以把这个map放入一块共享内存供多个进程操作,那该多方便呢。然而问题却没这么简单,由于STL帮我们完美的封装好了map的内部方法,包括内存分配方案,这也导致了我们没法把map直接简单的用placement new放置到已知的共享内存上。

要完成这个任务就需要我们自己实现一个基于共享内存的allocator,替换map默认的allocator,在这个allocator中实现map的内存分配方案。关于allocator的讲解可以看侯捷的《STL源码剖析》这本书,写一个简单的allocator直接套下边的模板就可以了,主要实现的是这几个函数:

  • max_size() 容器的最大容量
  • allocate(num) 为num个元素分配内存
  • construct(p) 将p所指的元素初始化
  • destroy(p) 销毁p所指向的元素
  • deallocate(p, num) 收回p所指的num个空间

下载: myalloc.hh
  1. #include <limits>
  2. #include <iostream>
  3.  
  4. namespace MyLib {
  5.    template <class T>
  6.    class MyAlloc {
  7.      public:
  8.        // type definitions
  9.        typedef T        value_type;
  10.        typedef T*       pointer;
  11.        typedef const T* const_pointer;
  12.        typedef T&       reference;
  13.        typedef const T& const_reference;
  14.        typedef std::size_t    size_type;
  15.        typedef std::ptrdiff_t difference_type;
  16.  
  17.        // rebind allocator to type U
  18.        template <class U>
  19.        struct rebind {
  20.            typedef MyAlloc<U> other;
  21.        };
  22.  
  23.        // return address of values
  24.        pointer address (reference value) const {
  25.            return &value;
  26.        }
  27.        const_pointer address (const_reference value) const {
  28.            return &value;
  29.        }
  30.  
  31.        /* constructors and destructor
  32.         * - nothing to do because the allocator has no state
  33.         */
  34.        MyAlloc() throw() {
  35.        }
  36.        MyAlloc(const MyAlloc&) throw() {
  37.        }
  38.        template <class U>
  39.          MyAlloc (const MyAlloc<U>&) throw() {
  40.        }
  41.        ~MyAlloc() throw() {
  42.        }
  43.  
  44.        // return maximum number of elements that can be allocated
  45.        size_type max_size () const throw() {
  46.            return std::numeric_limits<std::size_t>::max() / sizeof(T);
  47.        }
  48.  
  49.        // allocate but don't initialize num elements of type T
  50.        pointer allocate (size_type num, const void* = 0) {
  51.            // print message and allocate memory with global new
  52.            std::cerr << "allocate " << num << " element(s)"
  53.                      << " of size " << sizeof(T) << std::endl;
  54.            pointer ret = (pointer)(::operator new(num*sizeof(T)));
  55.            std::cerr << " allocated at: " << (void*)ret << std::endl;
  56.            return ret;
  57.        }
  58.  
  59.        // initialize elements of allocated storage p with value value
  60.        void construct (pointer p, const T& value) {
  61.            // initialize memory with placement new
  62.            new((void*)p)T(value);
  63.        }
  64.  
  65.        // destroy elements of initialized storage p
  66.        void destroy (pointer p) {
  67.            // destroy objects by calling their destructor
  68.            p->~T();
  69.        }
  70.  
  71.        // deallocate storage p of deleted elements
  72.        void deallocate (pointer p, size_type num) {
  73.            // print message and deallocate memory with global delete
  74.            std::cerr << "deallocate " << num << " element(s)"
  75.                      << " of size " << sizeof(T)
  76.                      << " at: " << (void*)p << std::endl;
  77.            ::operator delete((void*)p);
  78.        }
  79.    };
  80.  
  81.    // return that all specializations of this allocator are interchangeable
  82.    template <class T1, class T2>
  83.    bool operator== (const MyAlloc<T1>&,
  84.                     const MyAlloc<T2>&) throw() {
  85.        return true;
  86.    }
  87.    template <class T1, class T2>
  88.    bool operator!= (const MyAlloc<T1>&,
  89.                     const MyAlloc<T2>&) throw() {
  90.        return false;
  91.    }
  92. }

如果要使用这个allocator的话,可以这样:

  1. #include <vector>
  2. #include "myalloc.hh"
  3.  
  4. int main()
  5. {
  6.     // create a vector, using MyAlloc<> as allocator
  7.     std::vector<int,MyLib::MyAlloc<int> > v;
  8.  
  9.     // insert elements
  10.     // - causes reallocations
  11.     v.push_back(42);
  12. }

不过写出这样一个简单的vector内存分配器是很容易,不过如果要针对map或者hash_map写一份高效鲁棒的内存分配器却也不是一件容易的事情。一个想法就是在共享内存上划分出内存池,自己对这块内存池进行维护,并且还得考虑进程同步问题。然而,一件幸运的事情是,在boost项目第1.35个版本之后,已经增加了一个叫做boost::interprocess的库,其中包含了关于多线程和多进程的很多有用的辅助类库,当中也有为STL容器所写的allocator,这里有一系列非常棒的示例程序

不过其中有一个注意点,由于allocator是以类的形式提供的,所以当你在使用STL容器的时候,你需要保证allocator的实例没有被销毁,例如下边的函数希望产生mymap这个指针供其他函数使用,结果将是错误的。事实上,一旦该函数运行结束,mymap所指向的内存地址将会失效。

  1. typedef int    KeyType;
  2. typedef float  MappedType;
  3. typedef std::pair<const KeyType, MappedType> ValueType;
  4. typedef allocator<ValueType, managed_shared_memory::segment_manager> ShmemAllocator;
  5. typedef map<KeyType, MappedType, std::less<KeyType>, ShmemAllocator> MyMap;
  6.  
  7. MyMap* get_ptr(){
  8.     using namespace boost::interprocess;
  9.  
  10.     managed_shared_memory segment(create_only, "MySharedMemory", 65536);
  11.     ShmemAllocator alloc_inst (segment.get_segment_manager());
  12.  
  13.     //如果你希望把mymap当作参数传递到别的函数中去处理,请保证此时alloc_inst这个实例是存在的。
  14.     MyMap *mymap = segment.construct<MyMap>("MyMap")(std::less<KeyType>(),alloc_inst);
  15.     return mymap;
  16. }

另外由 managed_shared_memory 创建的共享内存默认属性是 600。总之,结合了STL和boost库,把STL容器放入共享内存中供多进程使用,就变成了一件非常容易而且愉快的事情。

Post a Comment

Your email is never published nor shared. Required fields are marked *
(verify code, case-insensitive)