Boost.Interprocess使用手册翻译之八:分配器,容器和内存分配算法(Allocators, containers and memory allocation algorithms)

时间:2022-09-08 23:56:11

八.分配器,容器和内存分配算法

进程间分配器介绍

独立存储节点分配器

自适应池节点分配器

托管内存片段上的进程间和容器

与Boost.Interprocess兼容的Boost容器

进程间分配器介绍

Boost.Interprocess分配器的特性

转换Boost.Interprocess分配器

分配器:一种通用的管理内存段分配器

如我们所视,Boost.Interprocess通过托管内存段(托管共享内存、托管映射文件等)提供原始的内存分配和对象构建,并且用户的第一批请求之一即是在托管共享内存中使用容器。为达此目的,Boost.Interprocess使用托管共享内存段的内存分配算法来构建一些内存分配计划,包括通用和节点分配器。

Boost.Interprocess的STL兼容分配器通过模板参数进行配置。分配器定义基于内存片管理器的void_pointer的它们自己的指针做为模板参数。当这些segment_manager::void_pointer是相对指针时(例如offset_ptr<void>),用户能够放置这些分配器至映射到不同进程的不同基地址的内存中。

Boost.Interprocess分配器的特性

容器分配器一般是默认构造的,因为它们是无状态的(stateless)。std::allocator和Boost.Pool的boost::pool_allocator/boost::fast_pool_allocator就是默认构造分配器的例子。

此外,Boost.Interprocess分配器需要从一个具体的内存片段而不是系统范围内的内存资源(例如堆)中分配内存。Boost.Interprocess分配器是有状态的(stateful),这意味着它们必须被配置为告诉它们哪里是共享内存或内存映射文件。

这些信息在编译时和运行时传递:分配器接受一个定义内存片段管理器类型的模板参数,并且它们的构造函数接受一个指向此内存片段管理器的指针,用户将在此管理器管理的内存上分配值。

Boost.Interprocess分配器没有默认构造函数,因此容器必须使用一个配置的分配器显式初始化:

//The allocators must be templatized with the segment manager type
typedef any_interprocess_allocator
<int, managed_shared_memory::segment_manager, ...> Allocator;

//The allocator must be constructed with a pointer to the segment manager
Allocator alloc_instance (segment.get_segment_manager(), ...);

//Containers must be initialized with a configured allocator
typedef my_list<int, Allocator> MyIntList;
MyIntList mylist(alloc_inst);

//This would lead to a compilation error, because
//the allocator has no default constructor
//MyIntList mylist;

Boost.Interprocess分配器也有一个get_segment_manager() 函数,它返回在构造函数中接收的底层内存片段管理器:

Allocator::segment_manager s = alloc_instance.get_segment_manager();
AnotherType *a = s->construct<AnotherType>(anonymous_instance)(/*Parameters*/);

转换Boost.Interprocess分配器

当转换STL容器时,需要积极讨论的是应该对分配器做些什么。一些STL实现,例如Visual .NET 2003的Dinkumware,当分配器不相同时,通过临时措施执行了整个容器的深转换。容器转换的一个提议解决方案是分配器转换时不应该抛出异常。

不幸的是,通过共享内存走不通这条路。使用堆内存分配,如果节点分配器的Group1共享一个通常的独立存储,Group2共享另一个通常的独立存储,则需要一个简单的指针转换用于转换Group1和Group2的分配器。但是如果用户想转换两个共享内存分配器,并且每个都位于不同的共享内存片段中,这将是不可能的。由于一般来说共享内存在每个进程中都映射在不同的地址空间上,一个片段中的指针不能指向位于另一个共享内存片段中的任何对象,因为在每个进程中,片段间的距离是不同的。然而,如果两个共享内存分配器是在同一片段中,一个不抛异常的转换是可能的,就好像堆内存分配器一样。

直到有一个最终的解决方案前,Boost.Interprocess分配器执行一个不抛异常的转换函数用于转换内部指针。如果一个位于共享内存片段的分配器与位于另外一个共享内存片段中的分配器进行转换,结果是未定义的。但基本不会崩溃。

分配器:一种通用的管理内存段分配器

allocator类定义了一个分配器类,它使用托管内存片段算法来分配和释放内存。它通过托管内存片段的片段管理来完成。该分配器相当于托管内存片段的标准std::分配器。allocator类使用分配对象和片段管理器进行模板化。

等价:采用相同片段管理器构建的两个allocator示例是等价的。如果一个实例使用拷贝构造构建,则此实例与原始的实例是等价的。

分配线程安全:分配和释放是通过调用片段管理的分配函数来执行的,因此分配器提供了与片段管理器同样的线程安全性。

要使用allocator,你首先必须包含头文件:

#include <boost/interprocess/allocators/allocator.hpp>

allocator具有如下声明:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager>
class allocator;

} //namespace interprocess {
} //namespace boost {

allocator仅提供必需的类型定义并且转发所有通过构造函数传入的片段管理的分配和释放请求,就好像std::allocator转发请求至操作符new[]。

使用allocator是直截了当的:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create an allocator that allocates ints from the managed segment
allocator<int, managed_shared_memory::segment_manager>
allocator_instance(segment.get_segment_manager());

//Copy constructed allocator is equal
allocator<int, managed_shared_memory::segment_manager>
allocator_instance2(allocator_instance);
assert(allocator_instance2 == allocator_instance);

//Allocate and deallocate memory for 100 ints
allocator_instance2.deallocate(allocator_instance.allocate(100), 100);

return 0;
}

 

独立存储节点分配器

独立存储节点分配器的额外参数和函数

节点分配器(node_allocator):一个进程间共享的独立存储

私有节点分配器(private_node_allocator):一个私有独立存储

缓存节点分配器(cached_node_allocator):缓冲节点来避免开销

对每个分配器可变大小的内存分配算法浪费了一些空间在管理信息上。有时,通常是对小对象,这是不可接受的。在一些分配和释放规划中,内存分配算法会导致托管内存片段碎片化,这将导致降低性能。当分配很多同样类型的对象时,一个简单的独立存储就变为一个快速的和空间友好的分配器,就好像Boost.Pool库解释地一样。

独立存储节点分配器从一个通常目的的内存分配上分配一大块内存,然后分割此大块内存为数个节点。没有簿记信息存储在节点中以达到最小内存浪费的目的:使用构建在此节点内存中的指针来连接空闲节点。

Boost.Interprocess提供3种基于此独立存储算法的分配器:node_allocatorprivate_node_allocatorcached_node_allocator

要获得独立存储池使用的详细信息,参考Implementation of Boost.Interprocess segregated storage pools 章节。

独立存储节点分配器的额外参数和函数

node_allocatorprivate_node_allocatorcached_node_allocator使用标准分配器接口和函数,它们在Propertiesof Boost.Interprocess allocators中解释。

所有这些分配器用3个参数模板化:

  • class T:分配的类型。
  • class SegmentManager:传入构造函数的片段管理器类型。
  • std::size_t NodesPerChunk:一个内存块将包含的节点数目。此值将定义当池子中的节点耗尽时,池子将向片段管理器申请的内存值。此参数有一个缺省值。

这些分配器还提供了deallocate_free_chunks()函数。此函数将遍历池子中所有的内存块,然后返还空闲内存块至托管内存片段中。如果未使用此函数,则直到池子被销毁才释放空闲内存块,因此在析构池子之前返回池子内存分配的唯一方法是手动调用此函数。此函数非常耗时,因为它具有平方时间复杂度(O(N^2))。

节点分配器(node_allocator):一个进程间共享的独立存储

对堆内存分配器(例如Boost.Pool的boost::fast_pool_allocator),对每个节点大小,通常使用一个全局的、线程共享的单例池。如果你尝试进程间共享一个节点分配器,这是不可能的。为达到这种共享目的,node_allocator使用了片段管理的唯一类型分配服务(参考 Uniqueinstance construction章节)。

在初始化中,一个node_allocator对象在内存片段中搜索此唯一对象。如果不存在,则构建一个。通过这种方式,所有构建在内存片段上的node_allocator对象共享一个唯一的内存池。

这种通用的独立存储模式不光在具有相同类型的node_allocator间共享,而且在所有具有相同大小的对象节点分配器间共享,例如node_allocator<uint32>和node_allocator<float32>。这种方式节省了很多内存,但也因每个节点分配引入了同步开销。

动态创建的通用独立存储集成了一个引用计数以便一个node_allocator能够知道是否有其他node_allocator关联了同样的通用独立存储。当最后一个关联池子的分配器销毁后,则池子被销毁。

等价:采用相同片段管理器构建的两个node_allocator实例是等价的。如果一个实例使用拷贝构造构建,则此实例与原始的实例是等价的。

分配线程安全:分配和释放是通过调用共享内存池来执行的。共享内存池提供了与片段管理器同样的线程安全性。

要使用node_allocator,你首先必须包含头文件:

#include <boost/interprocess/allocators/node_allocator.hpp>

node_allocator具有如下声明:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ...>
class node_allocator;

} //namespace interprocess {
} //namespace boost {

一个使用node_allocator的例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/node_allocator.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a node_allocator that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef node_allocator<int, managed_shared_memory::segment_manager>
node_allocator_t;
node_allocator_t allocator_instance(segment.get_segment_manager());

//Create another node_allocator. Since the segment manager address
//is the same, this node_allocator will be
//attached to the same pool so "allocator_instance2" can deallocate
//nodes allocated by "allocator_instance"
node_allocator_t allocator_instance2(segment.get_segment_manager());

//Create another node_allocator using copy-constructor. This
//node_allocator will also be attached to the same pool
node_allocator_t allocator_instance3(allocator_instance2);

//All allocators are equal
assert(allocator_instance == allocator_instance2);
assert(allocator_instance2 == allocator_instance3);

//So memory allocated with one can be deallocated with another
allocator_instance2.deallocate(allocator_instance.allocate(1), 1);
allocator_instance3.deallocate(allocator_instance2.allocate(1), 1);

//The common pool will be destroyed here, since no allocator is
//attached to the pool
return 0;
}

私有节点分配器(private_node_allocator):一个私有独立存储

如上述,node_allocator在节点分配器间共享独立存储,这些节点分配器分配具有相同大小的对象并,它优化了内存的使用。然而,这需要一个唯一/具名的对象构建特征以便这种共享是可能的。并且由于这种共享,还引入了同步开销至每次节点分配。有时,唯一对象服务是不可用的(例如,当构建一个索引类型来执行具名分配服务本身)或同步开销是不可接受的。在很多时候,程序员想保证当分配器销毁时,池子也一起销毁,以便能尽快的释放内存。

等价:两个private_node_allocator实例从来不会等价。用一种分配器分配的内存不能通过另一个分配器来释放。

分配线程安全:分配和释放不是线程安全的。

要使用private_node_allocator,你首先必须包含头文件:

#include <boost/interprocess/allocators/private_node_allocator.hpp>

private_node_allocator声明如下:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ...>
class private_node_allocator;

} //namespace interprocess {
} //namespace boost {
一个使用private_node_allocator的例子:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/private_node_allocator.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a private_node_allocator that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef private_node_allocator<int, managed_shared_memory::segment_manager>
private_node_allocator_t;
private_node_allocator_t allocator_instance(segment.get_segment_manager());

//Create another private_node_allocator.
private_node_allocator_t allocator_instance2(segment.get_segment_manager());

//Although the segment manager address
//is the same, this private_node_allocator will have its own pool so
//"allocator_instance2" CAN'T deallocate nodes allocated by "allocator_instance".
//"allocator_instance2" is NOT equal to "allocator_instance"
assert(allocator_instance != allocator_instance2);

//Create another node_allocator using copy-constructor.
private_node_allocator_t allocator_instance3(allocator_instance2);

//This allocator is also unequal to allocator_instance2
assert(allocator_instance2 != allocator_instance3);

//Pools are destroyed with the allocators
return 0;
}

缓存节点分配器(cached_node_allocator):缓冲节点来避免开销

对有些应用,node_allocator所有节点都共享会引入更大的开销,并且对其他一些应用,private_node_allocator的最小同步开销也会引入不能令人接受的内存消耗。

未解决这些问题,Boost.Interprocess提供了一个分配器,cached_node_allocator,它从通常的池子中分配节点,但缓存它们中的一些做为私有,以便之后的分配没有同步开销。当缓存区已满,分配器返回一些缓存节点至通常的池子中,因此它们就能被其他分配器使用了。

等价:两个采用相同片段管理器构建的两个cached_node_allocator实例是等价的。如果一个实例使用拷贝构造构建,则此实例与原始的实例是等价的。

分配线程安全:分配和释放不是线程安全的。

要使用cached_node_allocator,你首先必须包含头文件:

#include <boost/interprocess/allocators/cached_node_allocator.hpp>

cached_node_allocator声明如下:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ...>
class cached_node_allocator;

} //namespace interprocess {
} //namespace boost {

一个cached_node_allocator实例和一个node_allocator实例共享同样的池子,如果这两个实例接收了相同的模板参数。这意味着它们之一返回的共享池节点可以被另一个重用。请注意这并不意味着这两个分配器是等价的,这仅是给那些想最大化池子利用率的程序员一些信息。

cached_node_allocator提供了额外的函数来控制缓存区(缓存区能被各实例控制):

  • void set_max_cached_nodes(std::size_t n):设置缓存节点的最大限制数。如果缓存节点达到限制,一些节点将被返回至共享池。
  • std::size_t get_max_cached_nodes() const:返回缓存节点的最大限制数。
  • void deallocate_cache():返还缓存节点至共享池。

一个使用cached_node_allocator的例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/cached_node_allocator.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a cached_node_allocator that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef cached_node_allocator<int, managed_shared_memory::segment_manager>
cached_node_allocator_t;
cached_node_allocator_t allocator_instance(segment.get_segment_manager());

//The max cached nodes are configurable per instance
allocator_instance.set_max_cached_nodes(3);

//Create another cached_node_allocator. Since the segment manager address
//is the same, this cached_node_allocator will be
//attached to the same pool so "allocator_instance2" can deallocate
//nodes allocated by "allocator_instance"
cached_node_allocator_t allocator_instance2(segment.get_segment_manager());

//The max cached nodes are configurable per instance
allocator_instance2.set_max_cached_nodes(5);

//Create another cached_node_allocator using copy-constructor. This
//cached_node_allocator will also be attached to the same pool
cached_node_allocator_t allocator_instance3(allocator_instance2);

//We can clear the cache
allocator_instance3.deallocate_cache();

//All allocators are equal
assert(allocator_instance == allocator_instance2);
assert(allocator_instance2 == allocator_instance3);

//So memory allocated with one can be deallocated with another
allocator_instance2.deallocate(allocator_instance.allocate(1), 1);
allocator_instance3.deallocate(allocator_instance2.allocate(1), 1);

//The common pool will be destroyed here, since no allocator is
//attached to the pool
return 0;
}

自适应池节点分配器

自适应池节点分配器的额外参数和函数

自适应池(adaptive_pool):一个进程间共享的自适应池

私有自适应池(private_adaptive_pool):一个私有的自适应池

缓存自适应池(cached_adaptive_pool):避免同步开销

基于简单独立存储算法的节点分配器是具有空间效率并且是快速的,但它们有个问题:它们只能增长。每个已分配的节点避免任何存储额外数据的有效载荷,这导致了如下限制:当一个节点被释放了,它存储在一个空闲节点链表中,但内存并没有归还至片段管理器,因此一个已被释放的节点仅能被使用同样节点池的其他容器使用。

这种行为会导致问题,如果几个容器使用boost::interprocess::node_allocator来临时分配大量的对象,但它们最终存储其中的几个:节点池将会充满了不能被再次使用的节点,这导致了片段内存内的浪费。

基于分配器的自适应池以一些空间(开销可低至1%)和性能(能被大多数应用接受)的代价获得了返回内存片段节点空闲块的能力,以便它们能被其他容器或托管对象构造函数使用。要了解使用“自适应池”的详情,参考 Implementation of Boost.Intrusive adaptive pools 章节。

类似于基于节点分配器的独立存储,Boost.Interprocess提供了3个新分配器:adaptive_poolprivate_adaptive_poolcached_adaptive_pool

自适应池节点分配器的额外参数和函数

adaptive_poolprivate_adaptive_poolcached_adaptive_pool使用标准分配器接口和函数,它们在 Propertiesof Boost.Interprocess allocators中被解释。

所有这些分配器被4个参数模板化:

  • class T:被分配的类型。
  • class SegmentManager:传入构造函数的片段管理器类型。
  • std::size_t NodesPerChunk:一个内存块将包含的节点数目。此值将定义当池子中的节点耗尽时,池子将向片段管理器申请的内存值。此参数有一个缺省值。
  • std::size_t MaxFreeChunk:池子将承载的最大空闲块数目。如果限度达到了,池子返还内存块至片段管理器。此参数有一个缺省值。

这些分配器还提供了deallocate_free_chunks()函数。此函数将遍历池子中所有的内存块,然后返还空闲内存块至托管内存片段中。此函数比独立存储分配器中对应的函数快得多,因为自适应池算法对空闲内存块提供常量时间复杂度的访问。

自适应池(adaptive_pool):一个进程间共享的自适应池

与node_allocator一样,一个全局的、进程-线程池被用于每个节点大小。在初始化时,adaptive_pool在片段中搜索此池。如果不存在,则创建一个。自适应池使用一个唯一的名字来创建。自适应池也能在所有node_allocators间共享,这些node_allocators分配具有同样大小的对象,例如,adaptive_pool<uint32>和adaptive_pool<float32>。

当所有关联池子的分配器被销毁后,通常自适应池也被销毁。

等价:采用相同片段管理器构建的两个adaptive_pool实例是等价的。如果一个实例使用拷贝构造构建,则此实例与原始的实例是等价的。

分配线程安全:分配和释放是通过调用共享内存池来执行的。共享内存池提供了与片段管理器同样的线程安全性。

要使用adaptive_pool,你首先必须包含头文件:

#include <boost/interprocess/allocators/adaptive_pool.hpp>

adaptive_pool具有如下声明:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ..., std::size_t MaxFreeChunks = ...>
class adaptive_pool;

} //namespace interprocess {
} //namespace boost {

一个使用adaptive_pool的例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/adaptive_pool.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a adaptive_pool that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef adaptive_pool<int, managed_shared_memory::segment_manager>
adaptive_pool_t;
adaptive_pool_t allocator_instance(segment.get_segment_manager());

//Create another adaptive_pool. Since the segment manager address
//is the same, this adaptive_pool will be
//attached to the same pool so "allocator_instance2" can deallocate
//nodes allocated by "allocator_instance"
adaptive_pool_t allocator_instance2(segment.get_segment_manager());

//Create another adaptive_pool using copy-constructor. This
//adaptive_pool will also be attached to the same pool
adaptive_pool_t allocator_instance3(allocator_instance2);

//All allocators are equal
assert(allocator_instance == allocator_instance2);
assert(allocator_instance2 == allocator_instance3);

//So memory allocated with one can be deallocated with another
allocator_instance2.deallocate(allocator_instance.allocate(1), 1);
allocator_instance3.deallocate(allocator_instance2.allocate(1), 1);

//The common pool will be destroyed here, since no allocator is
//attached to the pool
return 0;
}

私有自适应池(private_adaptive_pool):一个私有的自适应池

private_node_allocator拥有一个私有独立存储池类似,private_adaptive_pool也拥有一个私有自适应池。如果用户想在容器中避免过度节点分配同步开销,private_adaptive_pool是一个很好的选择。

等价:两个private_adaptive_pool实例从来不会等价。用一种分配器分配的内存不能通过另一个分配器来释放。

分配线程安全:分配和释放不是线程安全的。

要使用private_adaptive_pool,你首先必须包含头文件:

#include <boost/interprocess/allocators/private_adaptive_pool.hpp>

private_adaptive_pool声明如下:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ..., std::size_t MaxFreeChunks = ...>
class private_adaptive_pool;

} //namespace interprocess {
} //namespace boost {

一个使用private_adaptive_pool的例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/private_adaptive_pool.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a private_adaptive_pool that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef private_adaptive_pool<int, managed_shared_memory::segment_manager>
private_adaptive_pool_t;
private_adaptive_pool_t allocator_instance(segment.get_segment_manager());

//Create another private_adaptive_pool.
private_adaptive_pool_t allocator_instance2(segment.get_segment_manager());

//Although the segment manager address
//is the same, this private_adaptive_pool will have its own pool so
//"allocator_instance2" CAN'T deallocate nodes allocated by "allocator_instance".
//"allocator_instance2" is NOT equal to "allocator_instance"
assert(allocator_instance != allocator_instance2);

//Create another adaptive_pool using copy-constructor.
private_adaptive_pool_t allocator_instance3(allocator_instance2);

//This allocator is also unequal to allocator_instance2
assert(allocator_instance2 != allocator_instance3);

//Pools are destroyed with the allocators
return 0;
}

缓存自适应池(cached_adaptive_pool):避免同步开销

自适应池也有一个缓存版本。在这种分配方式下,分配器缓存一些节点来避免共享自适应池的同步和簿记开销。cached_adaptive_pool从通常的自适应分配器中分配节点但缓存一些以便之后的分配没有同步开销。当缓存区已满,分配器返还一些缓存节点至常用池,然后这些节点就能被其他具有相同托管片段的cached_adaptive_poolsadaptive_pools使用。

等价:两个采用相同片段管理器构建的两个cached_adaptive_pool实例是等价的。如果一个实例使用拷贝构造构建,则此实例与原始的实例是等价的。

分配线程安全:分配和释放不是线程安全的。

要使用cached_adaptive_pool,你首先必须包含头文件:

#include <boost/interprocess/allocators/cached_adaptive_pool.hpp>

cached_adaptive_pool声明如下:

namespace boost {
namespace interprocess {

template<class T, class SegmentManager, std::size_t NodesPerChunk = ..., std::size_t MaxFreeNodes = ...>
class cached_adaptive_pool;

} //namespace interprocess {
} //namespace boost {

一个cached_adaptive_pool实例和一个adaptive_pool实例共享同样的池子,如果这两个实例接收了相同的模板参数。这意味着它们之一返回的共享池节点可以被另一个重用。请注意这并不意味着这两个分配器是等价的,这仅是给那些想最大化池子利用率的程序员一些信息。

cached_adaptive_pool提供了额外的函数来控制缓存区(缓存区能被各实例控制):

  • void set_max_cached_nodes(std::size_t n):设置缓存节点的最大限制数。如果缓存节点达到限度,一些节点将被返还至共享池。
  • std::size_t get_max_cached_nodes() const:返回缓存节点的最大限制数。
  • void deallocate_cache():返还缓存节点至共享池。

一个使用cached_adaptive_pool的例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/cached_adaptive_pool.hpp>
#include <cassert>

using namespace boost::interprocess;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Create a cached_adaptive_pool that allocates ints from the managed segment
//The number of chunks per segment is the default value
typedef cached_adaptive_pool<int, managed_shared_memory::segment_manager>
cached_adaptive_pool_t;
cached_adaptive_pool_t allocator_instance(segment.get_segment_manager());

//The max cached nodes are configurable per instance
allocator_instance.set_max_cached_nodes(3);

//Create another cached_adaptive_pool. Since the segment manager address
//is the same, this cached_adaptive_pool will be
//attached to the same pool so "allocator_instance2" can deallocate
//nodes allocated by "allocator_instance"
cached_adaptive_pool_t allocator_instance2(segment.get_segment_manager());

//The max cached nodes are configurable per instance
allocator_instance2.set_max_cached_nodes(5);

//Create another cached_adaptive_pool using copy-constructor. This
//cached_adaptive_pool will also be attached to the same pool
cached_adaptive_pool_t allocator_instance3(allocator_instance2);

//We can clear the cache
allocator_instance3.deallocate_cache();

//All allocators are equal
assert(allocator_instance == allocator_instance2);
assert(allocator_instance2 == allocator_instance3);

//So memory allocated with one can be deallocated with another
allocator_instance2.deallocate(allocator_instance.allocate(1), 1);
allocator_instance3.deallocate(allocator_instance2.allocate(1), 1);

//The common pool will be destroyed here, since no allocator is
//attached to the pool
return 0;
}

托管内存片段上的进程间和容器

Boost.Interprocess分配器的容器要求

托管内存片段上的STL容器

这些在哪里被分配?

进程间容器的移动语义

容器的容器

Boost.Interprocess分配器的容器要求

Boost.Interprocess STL兼容分配器提供了一个STL兼容分配器接口,并且如果他们定义了代表一个相对指针的内部指针,则它们能被用在共享内存、内存映射文件或用户自定义内存片段中的STL容器中。

然而,正如Scott Meyers在他的书籍《Effective STL》提到的第十项,“请注意分配器的约定和限制”:

  • “该标准明确允许库实现假设每个分配器的指针定义为T*的同义词”
  • “该标准阐述了一个STL实现是允许假设所有具有相同类型的分配器对象是等价的”

显然,如果一个STL实现忽略了指针定义,则没有智能指针能做为allocator::pointer使用。如果STL实现假设所有具有相同类型的分配器对象等价,它将假设这两个分配在不同内存池上的分配器等价,这完全是个灾难。

对我们想通过Boost.Interprocess放置在共享内存或内存映射文件中的STL容器,不能做任何这些假设,因此:

  • STL容器可能不会假设用某一分配器分配的内存能够被其他相同类型的分配器释放。所有的分配器对象仅当被一个对象分配的内存能能够被另一个释放时是等价的,并且这仅能在运行时通过==()来测试。
  • 容器的内部指针必须是allocator::pointer类型,并且容器不能假设allocator::pointer是一个原始指针。
  • 所有对象必须通过allocator::construct和allocator::destroy函数来构造和销毁。

管内存片段上的STL容器

不幸的是,许多STL实现对内部数据使用原始指针,忽略分配器指针类型定义和其他一些点的关于allocator::typedef是T*的假设。这是因为在实践中,对池/节点内存分配器不需要指针定义不是T*的分配器。

直到STL实现一个通用的方法处理allocator::pointer定义,Boost.Interprocess提供了如下类:

  • boost:interprocess::vector实现了std::vector,能被使用在托管内存片段例如共享内存上。使用它必须包含:
#include <boost/interprocess/containers/vector.hpp>
  • boost:interprocess::deque实现了std::deque,能被使用在托管内存片段例如共享内存上。使用它必须包含:
#include <boost/interprocess/containers/deque.hpp>
  • list实现了std::list,能被使用在托管内存片段例如共享内存上。使用它必须包含:
#include <boost/interprocess/containers/list.hpp>
  • slist实现了SGI的slist容器(单向链表),能被使用在托管内存片段例如共享内存上。使用它必须包含:
#include <boost/interprocess/containers/slist.hpp>
  • set/ multiset/ map/ multimap家族实现了std::set/multiset/map/multimap家族,能被使用在托管内存片段例如共享内存上。使用它必须包含:
#include <boost/interprocess/containers/set.hpp>
#include <boost/interprocess/containers/map.hpp>
  • flat_set/ flat_multiset/ flat_map/ flat_multimap类族是Loki库中的著名的Andrei Alexandrescu AssocVector 类的适应和扩展,能被使用在托管内存片段例如共享内存上。这些类采用有序容器实现,提供了与std::set/multiset/map/multimap同样的功能,相比基于红黑树的标准有序关联容器,它具有更快的查找速度,但插入操作会更慢。使用它必须包含:
#include <boost/interprocess/containers/flat_set.hpp>
#include <boost/interprocess/containers/flat_map.hpp>
  • basic_string实现了std::basic_string,能被使用在托管内存片段例如共享内存上。它是用了类似于vector的连续存储方式实现,因此它具有快速的c字符串转换功能并且能与vectorstream输入输出流格式类一起使用。使用它必须包含:
#include <boost/interprocess/containers/string.hpp>

所有这些容器具有与标准容器相同的缺省参数,并且它们能和其他非Boost.Interprocess分配器(例如std::allocator或boost::pool_allocator)一起使用。

为了将它们放置于托管内存片段内,我们必须使用Boost.Interprocess分配器定义分配器模板参数以便容器在托管内存片段上分配值。为将容器放置于共享内存上,我们在共享内存片段上构建它,就好像Boost.Interprocess任何其他对象一样:

#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>

int main ()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//A managed shared memory where we can construct objects
//associated with a c-string
managed_shared_memory segment(create_only,
"MySharedMemory", //segment name
65536);

//Alias an STL-like allocator of ints that allocates ints from the segment
typedef allocator<int, managed_shared_memory::segment_manager>
ShmemAllocator;

//Alias a vector that uses the previous STL-like allocator
typedef vector<int, ShmemAllocator> MyVector;

int initVal[] = {0, 1, 2, 3, 4, 5, 6 };
const int *begVal = initVal;
const int *endVal = initVal + sizeof(initVal)/sizeof(initVal[0]);

//Initialize the STL-like allocator
const ShmemAllocator alloc_inst (segment.get_segment_manager());

//Construct the vector in the shared memory segment with the STL-like allocator
//from a range of iterators
MyVector *myvector =
segment.construct<MyVector>
("MyVector")/*object name*/
(begVal /*first ctor parameter*/,
endVal /*second ctor parameter*/,
alloc_inst /*third ctor parameter*/);

//Use vector as your want
std::sort(myvector->rbegin(), myvector->rend());
// . . .
//When done, destroy and delete vector from the segment
segment.destroy<MyVector>("MyVector");
return 0;
}

这些容器也表明,创建/修改一个已经存在的容器使其能放置在共享内存上是多么地容易。

这些在哪里被分配?

要放置Boost.Interprocess容器在共享内存/内存映射文件等,需同时使用两种机制:

  • Boost.Interprocess的construct<>,find_or_construct<>等函数。这些函数放置一个C++对象在共享内存/内存映射文件中。但它仅放置对象,而不是对象可能会动态分配的内存。
  • 共享内存分配器。它们允许分配共享内存/内存映射文件块以便容器能够动态分配内存碎片来存储新插入的元素。

这意味着要放置任何Boost.Interprocess容器(包括Boost.Interprocess字符串)于共享内存或内存映射文件中,容器必须:

  • 定义它们自己的模板分配器参数到Boost.Interprocess分配器。
  • 每个容器的构造函数必须使用Boost.Interprocess分配器做参数。
  • 你必须使用construct<>/find_or_construct<>等函数来放置容器至托管内粗中。

如果你做了头两点但没使用 construct<>或find_or_construct<>,则你仅在你自己的进程中创建了一个容器但它为共享内存/内存映射文件中包含的类型分配了内存空间。

让我们看一个例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/allocators/allocator.hpp>

int main ()
{
using namespace boost::interprocess;
//Typedefs
typedef allocator<char, managed_shared_memory::segment_manager>
CharAllocator;
typedef basic_string<char, std::char_traits<char>, CharAllocator>
MyShmString;
typedef allocator<MyShmString, managed_shared_memory::segment_manager>
StringAllocator;
typedef vector<MyShmString, StringAllocator>
MyShmStringVector;

//Open shared memory
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

managed_shared_memory shm(create_only, "MySharedMemory", 10000);

//Create allocators
CharAllocator charallocator (shm.get_segment_manager());
StringAllocator stringallocator(shm.get_segment_manager());

//This string is in only in this process (the pointer pointing to the
//buffer that will hold the text is not in shared memory).
//But the buffer that will hold "this is my text" is allocated from
//shared memory
MyShmString mystring(charallocator);
mystring = "this is my text";

//This vector is only in this process (the pointer pointing to the
//buffer that will hold the MyShmString-s is not in shared memory).
//But the buffer that will hold 10 MyShmString-s is allocated from
//shared memory using StringAllocator. Since strings use a shared
//memory allocator (CharAllocator) the 10 buffers that hold
//"this is my text" text are also in shared memory.
MyShmStringVector myvector(stringallocator);
myvector.insert(myvector.begin(), 10, mystring);

//This vector is fully constructed in shared memory. All pointers
//buffers are constructed in the same shared memory segment
//This vector can be safely accessed from other processes.
MyShmStringVector *myshmvector =
shm.construct<MyShmStringVector>("myshmvector")(stringallocator);
myshmvector->insert(myshmvector->begin(), 10, mystring);

//Destroy vector. This will free all strings that the vector contains
shm.destroy_ptr(myshmvector);
return 0;
}

进程间容器的移动语义

Boost.Interprocess容器支持移动语义,这意味着容器的内容能从一个容器移至另一个,而不需要任何拷贝。源容器的内容被移动至目标容器,并且源容器保留为缺省构造状态。

当使用容器的容器,我们也能使用移动语义来插入对象至容器中,避免不必要的拷贝。

要转移一个容器的内容至另一个,使用boost::move()函数,就好像示例一样。更多关于支持移动语义的函数,参考Boost.Interprocess containers章节:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <cassert>

int main ()
{
using namespace boost::interprocess;

//Typedefs
typedef managed_shared_memory::segment_manager SegmentManager;
typedef allocator<char, SegmentManager> CharAllocator;
typedef basic_string<char, std::char_traits<char>
,CharAllocator> MyShmString;
typedef allocator<MyShmString, SegmentManager> StringAllocator;
typedef vector<MyShmString, StringAllocator> MyShmStringVector;

//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

managed_shared_memory shm(create_only, "MySharedMemory", 10000);

//Create allocators
CharAllocator charallocator (shm.get_segment_manager());
StringAllocator stringallocator(shm.get_segment_manager());

//Create a vector of strings in shared memory.
MyShmStringVector *myshmvector =
shm.construct<MyShmStringVector>("myshmvector")(stringallocator);

//Insert 50 strings in shared memory. The strings will be allocated
//only once and no string copy-constructor will be called when inserting
//strings, leading to a great performance.
MyShmString string_to_compare(charallocator);
string_to_compare = "this is a long, long, long, long, long, long, string...";

myshmvector->reserve(50);
for(int i = 0; i < 50; ++i){
MyShmString move_me(string_to_compare);
//In the following line, no string copy-constructor will be called.
//"move_me"'s contents will be transferred to the string created in
//the vector
myshmvector->push_back(boost::move(move_me));

//The source string is in default constructed state
assert(move_me.empty());

//The newly created string will be equal to the "move_me"'s old contents
assert(myshmvector->back() == string_to_compare);
}

//Now erase a string...
myshmvector->pop_back();

//...And insert one in the first position.
//No string copy-constructor or assignments will be called, but
//move constructors and move-assignments. No memory allocation
//function will be called in this operations!!
myshmvector->insert(myshmvector->begin(), boost::move(string_to_compare));

//Destroy vector. This will free all strings that the vector contains
shm.destroy_ptr(myshmvector);
return 0;
}

容器的容器

当创建容器的容器时,每个容器需要一个分配器。为避免使用数个复杂定义的分配器,我们可以使用空分配器提供的类型擦除和隐式转换空分配器至其他类型的能力。

这里我们有一个在共享内存中创建映射表(map)的例子。关键字是一个字符串,映射类型是一个存储数个容器的类:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>

using namespace boost::interprocess;

//Typedefs of allocators and containers
typedef managed_shared_memory::segment_manager segment_manager_t;
typedef allocator<void, segment_manager_t> void_allocator;
typedef allocator<int, segment_manager_t> int_allocator;
typedef vector<int, int_allocator> int_vector;
typedef allocator<int_vector, segment_manager_t> int_vector_allocator;
typedef vector<int_vector, int_vector_allocator> int_vector_vector;
typedef allocator<char, segment_manager_t> char_allocator;
typedef basic_string<char, std::char_traits<char>, char_allocator> char_string;

class complex_data
{
int id_;
char_string char_string_;
int_vector_vector int_vector_vector_;

public:
//Since void_allocator is convertible to any other allocator<T>, we can simplify
//the initialization taking just one allocator for all inner containers.
complex_data(int id, const char *name, const void_allocator &void_alloc)
: id_(id), char_string_(name, void_alloc), int_vector_vector_(void_alloc)
{}
//Other members...
};

//Definition of the map holding a string as key and complex_data as mapped type
typedef std::pair<const char_string, complex_data> map_value_type;
typedef std::pair<char_string, complex_data> movable_to_map_value_type;
typedef allocator<map_value_type, segment_manager_t> map_value_type_allocator;
typedef map< char_string, complex_data
, std::less<char_string>, map_value_type_allocator> complex_map_type;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,"MySharedMemory", 65536);

//An allocator convertible to any allocator<T, segment_manager_t> type
void_allocator alloc_inst (segment.get_segment_manager());

//Construct the shared memory map and fill it
complex_map_type *mymap = segment.construct<complex_map_type>
//(object name), (first ctor parameter, second ctor parameter)
("MyMap")(std::less<char_string>(), alloc_inst);

for(int i = 0; i < 100; ++i){
//Both key(string) and value(complex_data) need an allocator in their constructors
char_string key_object(alloc_inst);
complex_data mapped_object(i, "default_name", alloc_inst);
map_value_type value(key_object, mapped_object);
//Modify values and insert them in the map
mymap->insert(value);
}
return 0;
}

与Boost.Interprocess兼容的Boost容器

Boost无序容器

Boost.MultiIndex容器

如上提及,容器开发者可能需要改变它们的实现以便使它们与Boost.Interprocess兼容,因为原实现通常忽略了使用智能指针的分配器。希望几个Boost容器进程间兼容。

Boost无序容器

Boost.Unordered容器是进程间兼容的,因此程序员能存储hash容器在共享内存和内存映射文件中。下面是一个在共享内存中存储无序映射表的小例子:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>

#include <boost/unordered_map.hpp> //boost::unordered_map
#include <functional> //std::equal_to
#include <boost/functional/hash.hpp> //boost::hash

int main ()
{
using namespace boost::interprocess;
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only, "MySharedMemory", 65536);

//Note that unordered_map<Key, MappedType>'s value_type is std::pair<const Key, MappedType>,
//so the allocator must allocate that pair.
typedef int KeyType;
typedef float MappedType;
typedef std::pair<const int, float> ValueType;

//Typedef the allocator
typedef allocator<ValueType, managed_shared_memory::segment_manager> ShmemAllocator;

//Alias an unordered_map of ints that uses the previous STL-like allocator.
typedef boost::unordered_map
< KeyType , MappedType
, boost::hash<KeyType> ,std::equal_to<KeyType>
, ShmemAllocator>
MyHashMap;

//Construct a shared memory hash map.
//Note that the first parameter is the initial bucket count and
//after that, the hash function, the equality function and the allocator
MyHashMap *myhashmap = segment.construct<MyHashMap>("MyHashMap") //object name
( 3, boost::hash<int>(), std::equal_to<int>() //
, segment.get_allocator<ValueType>()); //allocator instance

//Insert data in the hash map
for(int i = 0; i < 100; ++i){
myhashmap->insert(ValueType(i, (float)i));
}
return 0;
}

Boost.MultiIndex容器

被广泛使用的Boost.MultiIndex库与Boost.Interprocess是兼容的,因此我们能在共享内存中构建非常好的数据库。在共享内存中构建数据库比在普通内存中难一些,这通常是因为这些数据库包含了字符串并且这些字符串需要放置在共享内存中。共享内存字符串需要一个分配器在它们构造函数中,因此这通常使得对象插入会复杂一些。

这里有个例子显式如何放置多索引容器至共享内存中:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>

using namespace boost::interprocess;
namespace bmi = boost::multi_index;

typedef managed_shared_memory::allocator<char>::type char_allocator;
typedef basic_string<char, std::char_traits<char>, char_allocator>shm_string;

//Data to insert in shared memory
struct employee
{
int id;
int age;
shm_string name;
employee( int id_
, int age_
, const char *name_
, const char_allocator &a)
: id(id_), age(age_), name(name_, a)
{}
};

//Tags
struct id{};
struct age{};
struct name{};

// Define a multi_index_container of employees with following indices:
// - a unique index sorted by employee::int,
// - a non-unique index sorted by employee::name,
// - a non-unique index sorted by employee::age.
typedef bmi::multi_index_container<
employee,
bmi::indexed_by<
bmi::ordered_unique
<bmi::tag<id>, BOOST_MULTI_INDEX_MEMBER(employee,int,id)>,
bmi::ordered_non_unique<
bmi::tag<name>,BOOST_MULTI_INDEX_MEMBER(employee,shm_string,name)>,
bmi::ordered_non_unique
<bmi::tag<age>, BOOST_MULTI_INDEX_MEMBER(employee,int,age)> >,
managed_shared_memory::allocator<employee>::type
> employee_set;

int main ()
{
//Remove shared memory on construction and destruction
struct shm_remove
{
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
} remover;

//Create shared memory
managed_shared_memory segment(create_only,"MySharedMemory", 65536);

//Construct the multi_index in shared memory
employee_set *es = segment.construct<employee_set>
("My MultiIndex Container") //Container's name in shared memory
( employee_set::ctor_args_list()
, segment.get_allocator<employee>()); //Ctor parameters

//Now insert elements
char_allocator ca(segment.get_allocator<char>());
es->insert(employee(0,31, "Joe", ca));
es->insert(employee(1,27, "Robert", ca));
es->insert(employee(2,40, "John", ca));
return 0;
}

程序员能放置Boost.CircularBuffer容器在共享内存中,定义BOOST_CB_DISABLE_DEBUG或更常用的NDEBUG禁止调试的功能。原因是这些调试工具仅与原始指针兼容。