Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Managed Memory Segments

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 16. Boost.Interprocess

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Как мы уже видели,Boost.Interprocessпредлагает некоторые базовые классы для создания общих объектов памяти и отображения файлов и отображения этих отображаемых классов в адресное пространство процесса.

Однако управление этими сегментами памяти не так просто для нетривиальных задач. Нанесенная на карту область является буфером памяти фиксированной длины, а создание и уничтожение объектов любого типа динамически требует большой работы, поскольку для выделения частей этого сегмента потребуется программирование алгоритма управления памятью. Много раз мы также хотим связать имена с объектами, созданными в общей памяти, чтобы все процессы могли найти объект с помощью имени.

Boost.Interprocessпредлагает 4 класса сегментов управляемой памяти:

  • Для управления разделяемой памятью отображается областьbasic_managed_shared_memoryкласса.
  • Управление картографированным файлом памятиbasic_managed_mapped_file.
  • Для управления кучей выделен буфер памяти<operator new>basic_managed_heap_memoryclass.
  • Для управления пользователем предусмотрен буфер фиксированного размераbasic_managed_external_bufferclass.

Первые два класса управляют сегментами памяти, которые могут быть разделены между процессами. В-третьих, полезно создавать сложные базы данных для отправки через другие механизмы, такие как очереди сообщений в другие процессы. Четвертый класс может управлять любым буфером памяти фиксированного размера. Первые два класса будут объяснены в следующих двух разделах.basic_managed_heap_memoryиbasic_managed_external_bufferбудут объяснены позже.

Наиболее важными услугами сегмента управляемой памяти являются:

  • Динамическое распределение частей памяти сегмента.
  • Построение объектов C++ в сегменте памяти. Эти объекты могут быть анонимными, или мы можем связать с ними имя.
  • Поиск возможностей для названных объектов.
  • Настройка многих функций: алгоритм распределения памяти, типы индексов или типы символов.
  • Атомные конструкции и разрушения таковы, что при разделении сегмента между двумя процессами невозможно создать два объекта, связанных с одним именем, упрощая синхронизацию.

ВсеBoost.Interprocessуправляемые классы сегментов памяти представляют собой шаблонизированные классы, которые могут быть настроены пользователем:

template
      <
         class CharType,
         class MemoryAlgorithm,
         template<class IndexConfig> class IndexType
      >
class basic_managed_shared_memory / basic_managed_mapped_file /
      basic_managed_heap_memory   / basic_external_buffer;

Эти классы могут быть настроены со следующими параметрами шаблона:

  • CharType— тип символа, который будет использоваться для идентификации созданных именованных объектов (например,charилиwchar_t).
  • Алгоритм памяти— алгоритм памяти, используемый для выделения частей сегмента (например, rbtree_best_fit). Внутренние типы алгоритма памяти также определяют:
    • Тип синхронизацииMemoryAlgorithm::mutex_family, используемый во всех операциях распределения. Это позволяет использовать мутексы, определяемые пользователем, или избегать внутренней блокировки (возможно, код будет синхронизирован пользователем).
    • Тип указателя (<MemoryAlgorithm::void_pointer>), используемый алгоритмом распределения памяти или дополнительными вспомогательными структурами (например, картой для поддержания ассоциаций объекта/имени). Все совместимые с STL распределители и контейнеры, которые будут использоваться с этим сегментом управляемой памяти, будут использовать этот тип указателя. Тип указателя определяет, может ли сегмент управляемой памяти быть отображен между несколькими процессами. Например, если<void_pointer>равно<offset_ptr<void>>, мы сможем отображать управляемый сегмент в разных базовых адресах в каждом процессе. Если бы<void_pointer>было<void*>, можно было бы использовать только фиксированный адрес.
    • Написание нового алгоритма распределения памятидля получения более подробной информации об алгоритмах памяти.
  • IndexType— это тип индекса, который будет использоваться для хранения ассоциации имени-объекта (например, карта, хэш-карта или упорядоченный вектор).

Таким образом, мы можем использовать строки<char>или<wchar_t>для идентификации созданных объектов C++ в сегменте памяти, мы можем подключить новые алгоритмы распределения общей памяти и использовать тип индекса, который лучше всего подходит для наших потребностей.

Как видно,basic_managed_shared_memoryпредлагает большое разнообразие настроек. Но для обычного пользователя требуется общая память с именем объекта по умолчанию. Из-за этогоBoost.Interprocessопределяет наиболее распространенные специализации управляемой совместной памяти:

//!Defines a managed shared memory with c-strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef
   basic_managed_shared_memory<char
                              ,/*Default memory algorithm defining offset_ptr<void> as void_pointer*/
                              ,/*Default index type*/>
   managed_shared_memory;
//!Defines a managed shared memory with wide strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef
   basic_managed_shared_memory<wchar_t
                              ,/*Default memory algorithm defining offset_ptr<void> as void_pointer*/
                              ,/*Default index type*/>
   wmanaged_shared_memory;

<managed_shared_memory>выделяет объекты в общей памяти, ассоциированной с c-струной, и<wmanaged_shared_memory>выделяет объекты в общей памяти, ассоциированной с нулевой строкой wchar_t. Оба определяют тип указателя как<offset_ptr<void>>, поэтому их можно использовать для отображения общей памяти на разных базовых адресах в разных процессах.

Если пользователь хочет сопоставить общую память в одном и том же адресе во всех процессах и хочет использовать необработанные указатели внутри, а не офсетные указатели,Boost.Interprocessопределяет следующие типы:

//!Defines a managed shared memory with c-strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes*/
typedef basic_managed_shared_memory
   <char
   ,/*Default memory algorithm defining void * as void_pointer*/
   ,/*Default index type*/>
fixed_managed_shared_memory;
//!Defines a managed shared memory with wide strings as keys for named objects,
//!the default memory algorithm (with process-shared mutexes,
//!and offset_ptr as internal pointers) as memory allocation algorithm
//!and the default index type as the index.
//!This class allows the shared memory to be mapped in different base
//!in different processes
typedef basic_managed_shared_memory
   <wchar_t
   ,/*Default memory algorithm defining void * as void_pointer*/
   ,/*Default index type*/>
wfixed_managed_shared_memory;

Управляемая общая память - это продвинутый класс, который объединяет объект общей памяти и отображаемую область, которая охватывает весь объект общей памяти. Это означает, что когда мысоздаемновую управляемую общую память:

  • Создан новый объект общей памяти.
  • Весь объект общей памяти отображается в адресном пространстве процесса.
  • Некоторые вспомогательные объекты построены (именно-объектный индекс, внутренние объекты синхронизации, внутренние переменные...) в картированной области для реализации функций управляемого сегмента памяти.

Когда мыоткрываемуправляемую общую память

  • Открывается объект общей памяти.
  • Весь объект общей памяти отображается в адресном пространстве процесса.

Чтобы использовать управляемую общую память, вы должны включить следующий заголовок:

#include <boost/interprocess/managed_shared_memory.hpp>
//1.  Creates a new shared memory object
//    called "MySharedMemory".
//2.  Maps the whole object to this
//    process' address space.
//3.  Constructs some objects in shared memory
//    to implement managed features.
//!!  If anything fails, throws interprocess_exception
//
managed_shared_memory segment      ( create_only
                                   , "MySharedMemory" //Shared memory object name
                                   , 65536);          //Shared memory object size in bytes
//1.  Opens a shared memory object
//    called "MySharedMemory".
//2.  Maps the whole object to this
//    process' address space.
//3.  Obtains pointers to constructed internal objects
//    to implement managed features.
//!!  If anything fails, throws interprocess_exception
//
managed_shared_memory segment      (open_only,       "MySharedMemory");//Shared memory object name
//1.  If the segment was previously created
//    equivalent to "open_only" (size is ignored).
//2.  Otherwise, equivalent to "create_only"
//!!  If anything fails, throws interprocess_exception
//
managed_shared_memory segment      ( open_or_create
                                   , "MySharedMemory" //Shared memory object name
                                   , 65536);          //Shared memory object size in bytes

Когда объект<managed_shared_memory>разрушается, объект совместно используемой памяти автоматически удаляется, и все ресурсы освобождаются. Для удаления объекта общей памяти из системы необходимо использовать функцию<shared_memory_object::remove>. Удаление объекта общей памяти может потерпеть неудачу, если какой-либо процесс все еще отображает объект общей памяти.

Пользователь также может отображать управляемую общую память в фиксированном адресе. Данный вариант необходим при использовании<fixed_managed_shared_memory>. Для этого просто добавьте картографический адрес в качестве дополнительного параметра:

fixed_managed_shared_memory segment      (open_only      ,"MyFixedAddressSharedMemory" //Shared memory object name
   ,(void*)0x30000000            //Mapping address

Пользователи Windows также могут использовать встроенную общую память Windows вместо портативной управляемой памяти<shared_memory_object>. Это достигается благодаря классу<basic_managed_windows_shared_memory>. Чтобы использовать его, просто включите:

#include <boost/interprocess/managed_windows_shared_memory.hpp>

Этот класс имеет тот же интерфейс, что и<basic_managed_shared_memory>, но использует встроенные окна общей памяти. Обратите внимание, что этот управляемый класс имеет те же проблемы со временем жизни, что и общая память Windows: когда последний процесс, подключенный к общей памяти Windows, отделяется от памяти (или заканчивается / сжимается), память разрушается. Таким образом, не существует постоянной поддержки совместно используемой памяти Windows.

Для связи между системными службами и пользовательскими приложениями с использованием<managed_windows_shared_memory>, пожалуйста, прочитайте объяснения, приведенные в главеНативные окна совместно используемой памяти.

Пользователи Unix также могут использовать XSI (система V) вместо портативной управляемой памяти<shared_memory_object>. Это достигается через класс<basic_managed_xsi_shared_memory>. Чтобы использовать его, просто включите:

#include <boost/interprocess/managed_xsi_shared_memory.hpp>

Этот класс имеет почти такой же интерфейс, как<basic_managed_shared_memory>, но использует общую память XSI в качестве бэкэнда.

Для получения дополнительной информации об управляемых возможностях совместной памяти XSI см.<basic_managed_xsi_shared_memory>ссылка на класс.

Как видно,basic_managed_mapped_fileпредлагает большое разнообразие настроек. Но для обычного пользователя требуется общая память с именем объекта по умолчанию. Из-за этогоBoost.Interprocessопределяет наиболее распространенные специализации управляемых картированных файлов:

//Named object creation managed memory segment
//All objects are constructed in the memory-mapped file
//   Names are c-strings,
//   Default memory management algorithm(rbtree_best_fit with no mutexes)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_mapped_file <
   char,
   rbtree_best_fit<mutex_family, offset_ptr<void> >,
   flat_map_index
   >  managed_mapped_file;
//Named object creation managed memory segment
//All objects are constructed in the memory-mapped file
//   Names are wide-strings,
//   Default memory management algorithm(rbtree_best_fit with no mutexes)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_mapped_file<
   wchar_t,
   rbtree_best_fit<mutex_family, offset_ptr<void> >,
   flat_map_index
   >  wmanaged_mapped_file;

<managed_mapped_file>выделяет объекты в картированных файлах памяти, связанных с c-струной, и<wmanaged_mapped_file>выделяет объекты в картированном файле памяти, связанном с нулевой строкой wchar_t. Оба определяют тип указателя как<offset_ptr<void>>, поэтому их можно использовать для отображения файла на разных базовых адресах в разных процессах.

Управляемый картографический файл - это продвинутый класс, который объединяет файл и отображаемую область, которая охватывает весь файл. Это означает, что когда мысоздаемновый управляемый картографический файл:

  • Создается новый файл.
  • Весь файл отображается в адресном пространстве процесса.
  • Некоторые вспомогательные объекты построены (именно-объектный индекс, внутренние объекты синхронизации, внутренние переменные...) в картированной области для реализации функций управляемого сегмента памяти.

Когда мыоткрываемуправляемый картированный файл

  • Открывается файл.
  • Весь файл отображается в адресном пространстве процесса.

Чтобы использовать управляемый картографический файл, вы должны включить следующий заголовок:

#include <boost/interprocess/managed_mapped_file.hpp>
//1.  Creates a new file
//    called "MyMappedFile".
//2.  Maps the whole file to this
//    process' address space.
//3.  Constructs some objects in the memory mapped
//    file to implement managed features.
//!!  If anything fails, throws interprocess_exception
//
managed_mapped_file mfile      (create_only,      "MyMappedFile",   //Mapped file name      65536);           //Mapped file size
//1.  Opens a file
//    called "MyMappedFile".
//2.  Maps the whole file to this
//    process' address space.
//3.  Obtains pointers to constructed internal objects
//    to implement managed features.
//!!  If anything fails, throws interprocess_exception
//
managed_mapped_file mfile      (open_only,      "MyMappedFile");  //Mapped file name[c++]
//1.  If the file was previously created
//    equivalent to "open_only".
//2.  Otherwise, equivalent to "open_only" (size is ignored)
//
//!!  If anything fails, throws interprocess_exception
//
managed_mapped_file mfile      (open_or_create,      "MyMappedFile",   //Mapped file name      65536);           //Mapped file size

Когда объект<managed_mapped_file>разрушается, файл автоматически демапируется, и все ресурсы освобождаются. Чтобы удалить файл из файловой системы, вы можете использовать стандартные функции C<std::remove>илиBoost.Filesystem<remove()>, но удаление файла может потерпеть неудачу, если какой-либо процесс все еще отображается в памяти или файл открыт любым процессом.

Чтобы получить более портативное поведение, используйте операцию<file_mapping::remove(constchar*)>, которая удалит файл, даже если он отображается. Однако в некоторых системах ОС удаление не произойдет, если файл (например, с помощью потоков файлов C++) и файлу не было предоставлено разрешение на совместное удаление. Но в большинстве случаев<file_mapping::remove>достаточно портативно.

Для получения дополнительной информации об управляемых картографических возможностях файлов см.<basic_managed_mapped_file>ссылка на класс.

Следующие функции являются общими для всех классов сегментов управляемой памяти, но мы будем использовать управляемую общую память в наших примерах. То же самое можно сделать с картографированными файлами памяти или другими классами сегментов управляемой памяти.

Если необходимо базовое выделение необработанных байтов из сегмента управляемой памяти (например, управляемой совместно используемой памяти) для реализации межпроцессных коммуникаций верхнего уровня, этот класс предлагаетвыделитьираспределитьфункции. Функция распределения поставляется с броском и без бросковых версий. Бросающая версия добавляет импульс::interprocess::bad_alloc (который происходит от<std::bad_alloc>), если больше нет памяти и небросающая версия возвращает 0 указателя.

#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;
   //Managed memory segment that allocates portions of a shared memory
   //segment with the default management algorithm
   managed_shared_memory managed_shm(create_only,"MySharedMemory", 65536);
   //Allocate 100 bytes of memory from segment, throwing version
   void *ptr = managed_shm.allocate(100);
   //Deallocate it
   managed_shm.deallocate(ptr);
   //Non throwing version
   ptr = managed_shm.allocate(100, std::nothrow);
   //Deallocate it
   managed_shm.deallocate(ptr);
   return 0;
}

Класс также предлагает конверсии между абсолютными адресами, которые относятся к сегменту управляемой памяти, и ручкой, которая может быть передана с использованием любого межпроцессного механизма. Эта ручка может быть снова преобразована в абсолютный адрес с использованием сегмента управляемой памяти, который также содержит этот объект. Ручки могут использоваться в качестве ключей между процессами для идентификации выделенных частей управляемого сегмента памяти или объектов, построенных в управляемом сегменте.

//Process A obtains the offset of the address
managed_shared_memory::handle handle =
   segment.get_handle_from_address(processA_address);
//Process A sends this address using any mechanism to process B
//Process B obtains the handle and transforms it again to an address
managed_shared_memory::handle handle = ...
void * processB_address = segment.get_address_from_handle(handle);

При построении объектов в сегменте управляемой памяти (управляемой общей памяти, управляемых картированных файлов ...), связанных с именем, у пользователя есть разнообразное семейство объектов для «конструирования» или «конструирования, если не найдено».Boost.Interprocessможет построить один объект или массив объектов. Массив может быть построен с одинаковыми параметрами для всех объектов или мы можем определить каждый параметр из списка итераторов:

//!Allocates and constructs an object of type MyType (throwing version)
MyType *ptr = managed_memory_segment.construct<MyType>("Name") (par1, par2...);
//!Allocates and constructs an array of objects of type MyType (throwing version)
//!Each object receives the same parameters (par1, par2, ...)
MyType *ptr = managed_memory_segment.construct<MyType>("Name")[count](par1, par2...);
//!Tries to find a previously created object. If not present, allocates
//!and constructs an object of type MyType (throwing version)
MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name") (par1, par2...);
//!Tries to find a previously created object. If not present, allocates and
//!constructs an array of objects of type MyType (throwing version). Each object
//!receives the same parameters (par1, par2, ...)
MyType *ptr = managed_memory_segment.find_or_construct<MyType>("Name")[count](par1, par2...);
//!Allocates and constructs an array of objects of type MyType (throwing version)
//!Each object receives parameters returned with the expression (*it1++, *it2++,... )
MyType *ptr = managed_memory_segment.construct_it<MyType>("Name")[count](it1, it2...);
//!Tries to find a previously created object. If not present, allocates and constructs
//!an array of objects of type MyType (throwing version).  Each object receives
//!parameters returned with the expression (*it1++, *it2++,... )
MyType *ptr = managed_memory_segment.find_or_construct_it<MyType>("Name")[count](it1, it2...);
//!Tries to find a previously created object. Returns a pointer to the object and the
//!count (if it is not an array, returns 1). If not present, the returned pointer is 0
std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>("Name");
//!Destroys the created object, returns false if not present
bool destroyed = managed_memory_segment.destroy<MyType>("Name");
//!Destroys the created object via pointer
managed_memory_segment.destroy_ptr(ptr);

Все эти функции имеют небросковую версию, которая вызывается дополнительным параметром std::nothrow. Например, для простой конструкции объекта:

//!Allocates and constructs an object of type MyType (no throwing version)
MyType *ptr = managed_memory_segment.construct<MyType>("Name", std::nothrow) (par1, par2...);

Иногда пользователь не хочет создавать объекты класса, связанные с именем. Для этогоBoost.Interprocessможет создавать анонимные объекты в управляемом сегменте памяти. Все названные функции построения объектов доступны для создания анонимных объектов. Чтобы выделить анонимные объекты, пользователь должен использовать имя «boost::interprocess::anonymous_instance» вместо обычного имени:

MyType *ptr = managed_memory_segment.construct<MyType>(anonymous_instance) (par1, par2...);
//Other construct variants can also be used (including non-throwing ones)
...
//We can only destroy the anonymous object via pointer
managed_memory_segment.destroy_ptr(ptr);

Найти функции здесь не имеет смысла, поскольку анонимные объекты не имеют имени. Мы можем уничтожить анонимный объект только с помощью указателя.

Иногда пользователь хочет эмулировать синглтон в сегменте управляемой памяти. Очевидно, что, поскольку сегмент управляемой памяти построен во время выполнения, пользователь должен конструировать и уничтожать этот объект явно. Но как пользователь может быть уверен, что объект является единственным объектом своего типа в сегменте управляемой памяти? Это можно эмулировать с помощью именованного объекта и проверить, присутствует ли он, прежде чем пытаться создать его, но все процессы должны согласовываться в имени объекта, что также может противоречить другим существующим именам.

Для решения этой задачиBoost.Interprocessпредлагает создание «уникального объекта» в сегменте управляемой памяти. Только один экземпляр класса может быть создан в сегменте управляемой памяти с использованием этого сервиса «уникальный объект» (вы можете создать больше названных объектов этого класса, хотя), поэтому он облегчает эмуляцию однотонных объектов в процессах, например, для проектирования объединенных общих распределителей памяти. Объект можно искать, используя в качестве ключа тип класса.

// Construct
MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...);
// Find it
std::pair<MyType *,std::size_t> ret = managed_memory_segment.find<MyType>(unique_instance);
// Destroy it
managed_memory_segment.destroy<MyType>(unique_instance);
// Other construct and find variants can also be used (including non-throwing ones)
//...
// We can also destroy the unique object via pointer
MyType *ptr = managed_memory_segment.construct<MyType>(unique_instance) (par1, par2...);
managed_shared_memory.destroy_ptr(ptr);

Функция нахождения получает указатель на единственный объект типа Т, который может быть создан с помощью этого механизма «уникального экземпляра».

Одной из особенностей названных/уникальных выделений/поисков/разрушений является то, что ониатомные. Названные распределения используют схему рекурсивной синхронизации, определенную внутренним<mutex_family>типдефом, определенным параметром шаблона алгоритма распределения памяти<MemoryAlgorithm>. То есть тип мутекса, используемый для синхронизации названных/уникальных выделений, определяется типом<MemoryAlgorithm::mutex_family::recursive_mutex_type>. Для совместно используемой памяти и отображенных в памяти управляемых сегментов на основе файлов этот рекурсивный мутекс определяется как<interprocess_recursive_mutex>.

Если два процесса могут вызвать:

MyType *ptr = managed_shared_memory.find_or_construct<MyType>("Name")[count](par1, par2...);

В то же время только один процесс создает объект, а другой получает указатель на созданный объект.

Сырое выделение с использованием<allocate()>также можно безопасно назвать при выполнении именованных/анонимных/уникальных выделений, так же, как при программировании многопоточного приложения, вставляющего объект в защищенную мутексом карту, не блокирует другие потоки от вызова нового[], в то время как поток карты ищет место, где он должен вставить новый объект. Синхронизация происходит после того, как карта находит правильное место, и она должна выделить необработанную память для создания нового значения.

Это означает, что если мы создаем или ищем много именованных объектов, мы блокируем создание/исследование только от других процессов, но мы не блокируем другой процесс, если этот процесс вставляет элементы в общий вектор памяти.

Как видно, управляемые сегменты памяти при создании именованных объектов хранят в индексе ассоциацию имя/объект. Индекс представляет собой карту с названием объекта в качестве ключа и указателем на объект в качестве нанесенного на карту типа. Специализации по умолчаниюmanaged_shared_memoryиwmanaged_shared_memory, используютflat_map_indexв качестве индексного типа.

Каждый индекс имеет свои особенности, такие как время поиска, время вставки, время удаления, использование памяти и шаблоны распределения памяти.Boost.Interprocessпредлагает 3 типа индексов прямо сейчас:

  • boost::interprocess::flat_map_index flat_map_index: На основе boost::interprocess::flat_map, упорядоченный вектор, подобный классу AssocVector библиотеки Локи, предлагает отличное время поиска и минимальное использование памяти. Но вектор должен быть перераспределен, когда он заполнен, поэтому все данные должны быть скопированы в новый буфер. Идеально, когда вставки в основном во время инициализации, а во время выполнения нам просто нужны поиски.
  • boost::interprocess::map_index map_index: На основе boost::interprocess::map, управляемая память готова к версии std::map. Поскольку это контейнер на основе узла, он не имеет перераспределений, дерево должно быть просто перебалансировано. Предлагает уравновешенное время ввода/удаления/поиска с большим количеством накладных расходов на узел по сравнению сповышением::interprocess::flat_map_index. Идеально, когда поиски / вставки / удаления находятся в случайном порядке.
  • boost::interprocess::null_index null_index: Этот индекс предназначен для людей, использующих управляемый сегмент памяти только для необработанных выделений буфера памяти, и они не используют названные / уникальные выделения. Этот класс просто пуст и экономит пространство и время компиляции. Если вы попытаетесь использовать именованное создание объектов с управляемым сегментом памяти с помощью этого индекса, вы получите ошибку компиляции.

Например, если мы хотим определить новый класс управляемой совместной памяти, используяboost::interprocess::mapкак тип индекса, мы просто должны указать [boost::interprocess:::map_index map_index] как параметр шаблона:

//This managed memory segment can allocate objects with:
// -> a wchar_t string as key
// -> boost::interprocess::rbtree_best_fit with process-shared mutexes
//       as memory allocation algorithm.
// -> boost::interprocess::map<...> as the index to store name/object mappings
//
typedef boost::interprocess::basic_managed_shared_memory
         <  wchar_t
         ,  boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family, offset_ptr<void> >
         ,  boost::interprocess::map_index
         >  my_managed_shared_memory;

Boost.Interprocessпланирует предложить индексunordered_map, как только этот контейнер будет включен в Boost. Если этих индексов вам недостаточно, вы можете определить свой собственный тип индекса. Чтобы узнать, как это сделать, перейдите кСоздание пользовательских индексовраздел.

ВсеBoost.Interprocessуправляемые классы сегментов памяти строят в своих соответствующих сегментах памяти (общая память, сопоставленные с памятью файлы, куча памяти...) некоторые структуры для реализации алгоритма управления памятью, названные распределения, объекты синхронизации... Все эти объекты инкапсулированы в единый объект под названиемменеджер сегментов. Управляемый файл отображения памяти и управляемая общая память используют один и тот жеменеджер сегментовдля реализации всех функций сегмента управляемой памяти, в связи с тем, чтоменеджер сегментовявляется классом, который управляет буфером памяти фиксированного размера. Поскольку доступ как к общей памяти, так и к картированным файлам памяти осуществляется через картографированную область, а картированная область представляет собой буфер памяти фиксированного размера, один классменеджера сегментовможет управлять несколькими типами сегментов управляемой памяти.

Некоторые классыBoost.Interprocessтребуют указателя на менеджер сегмента в своих конструкторах, и менеджер сегмента может быть получен из любого сегмента управляемой памяти с использованием члена<get_segment_manager>:

managed_shared_memory::segment_manager *seg_manager =
   managed_shm.get_segment_manager();

Как только объект построен с использованием семейства функций<construct<>>, программист может получить информацию об объекте с помощью указателя на объект. Программист может получить следующую информацию:

  • Название объекта: Если это именованный экземпляр, имя, используемое в функции построения, возвращается, в противном случае возвращается 0.
  • Длина объекта: возвращает количество элементов объекта (1, если это одно значение, >=1, если это массив).
  • Тип конструкции: был ли объект построен с использованием названной, уникальной или анонимной конструкции.

Вот пример, показывающий эту функциональность:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
#include <cstring>
class my_class
{
   //...
};
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;
   managed_shared_memory managed_shm(create_only, "MySharedMemory", 10000*sizeof(std::size_t));
   //Construct objects
   my_class *named_object  = managed_shm.construct<my_class>("Object name")[1]();
   my_class *unique_object = managed_shm.construct<my_class>(unique_instance)[2]();
   my_class *anon_object   = managed_shm.construct<my_class>(anonymous_instance)[3]();
   //Now test "get_instance_name" function.
   assert(0 == std::strcmp(managed_shared_memory::get_instance_name(named_object), "Object name"));
   assert(0 == std::strcmp(managed_shared_memory::get_instance_name(unique_object), typeid(my_class).name()));
   assert(0 == managed_shared_memory::get_instance_name(anon_object));
   //Now test "get_instance_type" function.
   assert(named_type     == managed_shared_memory::get_instance_type(named_object));
   assert(unique_type    == managed_shared_memory::get_instance_type(unique_object));
   assert(anonymous_type == managed_shared_memory::get_instance_type(anon_object));
   //Now test "get_instance_length" function.
   assert(1 == managed_shared_memory::get_instance_length(named_object));
   assert(2 == managed_shared_memory::get_instance_length(unique_object));
   assert(3 == managed_shared_memory::get_instance_length(anon_object));
   managed_shm.destroy_ptr(named_object);
   managed_shm.destroy_ptr(unique_object);
   managed_shm.destroy_ptr(anon_object);
   return 0;
}

Иногда программист должен выполнить некоторый код и должен выполнить его с гарантией того, что никакой другой процесс или поток не будет создавать или уничтожать какой-либо именованный, уникальный или анонимный объект при выполнении функтора. Пользователь может захотеть создать несколько названных объектов и инициализировать их, но эти объекты должны быть доступны для остальных процессов сразу.

Для этого программист может использовать функцию<atomic_func()>, предлагаемую управляемыми классами:

//This object function will create several named objects
create_several_objects_func func(/**/);
//While executing the function, no other process will be
//able to create or destroy objects
managed_memory.atomic_func(func);

Обратите внимание, что<atomic_func>не мешает другим процессам выделять необработанную память или выполнять функции-члены для уже построенных объектов (например, другой процесс может подталкивать элементы в вектор, помещенный в сегмент). Атомная функция блокирует только названное, уникальное и анонимное создание, поиск и разрушение (совместные вызовы к<construct<>>,<find<>>,<find_or_construct<>>,<destroy<>>...) от других процессов.

Эти функции доступны для получения информации об управляемых сегментах памяти:

Получите размер сегмента памяти:

managed_shm.get_size();

Получите количество свободных байтов сегмента:

managed_shm.get_free_memory();

Очистить до нуля свободную память:

managed_shm.zero_free_memory();

Знайте, если вся память была размещена, ложно иначе:

managed_shm.all_memory_deallocated();

Тестирование внутренних структур управляемого сегмента. Вернуть верно, если не обнаружены ошибки:

managed_shm.check_sanity();

Получить количество названных и уникальных объектов, выделенных в сегменте:

managed_shm.get_num_named_objects();
managed_shm.get_num_unique_objects();

После создания управляемого сегмента управляемый сегмент не может быть выращен. Ограничение нелегко разрешимо: каждый процесс, подключенный к управляемому сегменту, должен быть остановлен, уведомлен о новом размере, им нужно будет переназначить управляемый сегмент и продолжить работу. Почти невозможно достичь с помощью библиотеки пользовательского уровня без помощи ядра операционной системы.

С другой стороны,Boost.Interprocessпредлагает рост офлайн-сегмента. Что это значит? Сегмент можно вырастить, если ни один процесс не отображает управляемый сегмент. Если приложение может найти момент, когда процесс не подключен, оно может расти или уменьшаться, чтобы соответствовать управляемому сегменту.

Вот пример, показывающий, как расти и сжиматься, чтобы соответствовать<managed_shared_memory>:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <cassert>
class MyClass
{
   //...
};
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 a managed shared memory
      managed_shared_memory shm(create_only, "MySharedMemory", 1000);
      //Check size
      assert(shm.get_size() == 1000);
      //Construct a named object
      MyClass *myclass = shm.construct<MyClass>("MyClass")();
      //The managed segment is unmapped here
   }
   {
      //Now that the segment is not mapped grow it adding extra 500 bytes
      managed_shared_memory::grow("MySharedMemory", 500);
      //Map it again
      managed_shared_memory shm(open_only, "MySharedMemory");
      //Check size
      assert(shm.get_size() == 1500);
      //Check "MyClass" is still there
      MyClass *myclass = shm.find<MyClass>("MyClass").first;
      assert(myclass != 0);
      //The managed segment is unmapped here
   }
   {
      //Now minimize the size of the segment
      managed_shared_memory::shrink_to_fit("MySharedMemory");
      //Map it again
      managed_shared_memory shm(open_only, "MySharedMemory");
      //Check size
      assert(shm.get_size() < 1000);
      //Check "MyClass" is still there
      MyClass *myclass = shm.find<MyClass>("MyClass").first;
      assert(myclass != 0);
      //The managed segment is unmapped here
   }
   return 0;
}

<managed_mapped_file>также предлагает аналогичную функцию для роста или сокращения_to_fit управляемого файла. Пожалуйста, помните, чтони один процесс не должен модифицировать файл / общую память, пока процесс роста / сокращения выполняется. В противном случае управляемый сегмент будет поврежден.

Как уже упоминалось, управляемый сегмент хранит информацию о названных и уникальных объектах в двух индексах. В зависимости от типа этих индексов, индекс должен перераспределять некоторые вспомогательные структуры при внесении новых названий или уникальных распределений. Для некоторых индексов, если пользователь знает, сколько именованных или уникальных объектов будет создано, можно выделить некоторые структуры, чтобы получить гораздо лучшую производительность. (Если индекс является упорядоченным вектором, он может предварительно распределить память, чтобы избежать перераспределения). Если индекс представляет собой хеш-структуру, он может предварительно распределить массив ковша.

Следующие функции резервируют память, чтобы сделать последующее распределение названных или уникальных объектов более эффективным. Эти функции полезны только для псевдоинтрузивных или неузловых индексов (например,<flat_map_index>,<iunordered_set_index>). Эти функции не имеют эффекта с индексом по умолчанию<iset_index>или другими индексами<map_index>:

managed_shm.reserve_named_objects(1000);
managed_shm.reserve_unique_objects(1000);
managed_shm.reserve_named_objects(1000);
managed_shm.reserve_unique_objects(1000);

Сегменты управляемой памяти также предлагают возможность итерации через построенные именованные и уникальные объекты для отладки.Предупреждение: эта итерация не является нить-безопасной, поэтому пользователь должен убедиться, что ни одна другая нить не манипулирует названными или уникальными индексами (создание, стирание, резервирование ...) в сегменте. Другие операции, не связанные с индексами, могут выполняться одновременно (например, выделение/распределение оперативной памяти).

Следующие функции возвращают постоянные итераторы в диапазон именованных и уникальных объектов, хранящихся в управляемом сегменте. В зависимости от типа индекса, итераторы могут быть признаны недействительными после названной или уникальной операции создания/стирания/запаса:

typedef managed_shared_memory::const_named_iterator const_named_it;
const_named_it named_beg = managed_shm.named_begin();
const_named_it named_end = managed_shm.named_end();
typedef managed_shared_memory::const_unique_iterator const_unique_it;
const_unique_it unique_beg = managed_shm.unique_begin();
const_unique_it unique_end = managed_shm.unique_end();
for(; named_beg != named_end; ++named_beg){
   //A pointer to the name of the named object
   const managed_shared_memory::char_type *name = named_beg->name();
   //The length of the name
   std::size_t name_len = named_beg->name_length();
   //A constant void pointer to the named object
   const void *value = named_beg->value();
}
for(; unique_beg != unique_end; ++unique_beg){
   //The typeid(T).name() of the unique object
   const char *typeid_name = unique_beg->name();
   //The length of the name
   std::size_t name_len = unique_beg->name_length();
   //A constant void pointer to the unique object
   const void *value = unique_beg->value();
}

Иногда интересно иметь возможность выделять выровненные фрагменты памяти из-за некоторых аппаратных или программных ограничений. Иногда выравнивание памяти является функцией, которая может быть использована для улучшения нескольких алгоритмов памяти.

Это распределение аналогично ранее показанному исходному распределению памяти, но требует дополнительного параметра, определяющего выравнивание. Есть ограничение для выравнивания:выравнивание должно быть мощностью двух.

Если пользователь хочет выделить много выровненных блоков (например, выровненных до 128 байт), размер, который минимизирует отходы памяти, является значением, которое почти кратно этому выравниванию (например, 2*128 - некоторые байты). Причина этого в том, что для каждого выделения памяти обычно требуются некоторые дополнительные метаданные в первых байтах выделенного буфера. Если пользователь может знать значение «некоторых байтов» и если первые байты свободного блока памяти используются для выполнения выровненного распределения, остальная часть блока может быть оставлена также выровненной и готовой к следующему выровненного распределения. Обратите внимание, что запросразмера, кратного выравниванию, не является оптимальным, поскольку оставляет следующий блок памяти невыровненным из-за необходимых метаданных.

Как только программист узнает размер полезной нагрузки каждого выделения памяти, он может запросить размер, который будет оптимальным для выделения выровненных кусков памяти, максимизируя как размер запроса, так ивозможности будущих выровненных распределений. Эта информация хранится в постоянной PayloadPerAllocation сегментов управляемой памяти.

Вот небольшой пример, показывающий, как выравнивается распределение:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
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;
   //Managed memory segment that allocates portions of a shared memory
   //segment with the default management algorithm
   managed_shared_memory managed_shm(create_only, "MySharedMemory", 65536);
   const std::size_t Alignment = 128;
   //Allocate 100 bytes aligned to Alignment from segment, throwing version
   void *ptr = managed_shm.allocate_aligned(100, Alignment);
   //Check alignment
   assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
   //Deallocate it
   managed_shm.deallocate(ptr);
   //Non throwing version
   ptr = managed_shm.allocate_aligned(100, Alignment, std::nothrow);
   //Check alignment
   assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
   //Deallocate it
   managed_shm.deallocate(ptr);
   //If we want to efficiently allocate aligned blocks of memory
   //use managed_shared_memory::PayloadPerAllocation value
   assert(Alignment > managed_shared_memory::PayloadPerAllocation);
   //This allocation will maximize the size of the aligned memory
   //and will increase the possibility of finding more aligned memory
   ptr = managed_shm.allocate_aligned
      (3*Alignment - managed_shared_memory::PayloadPerAllocation, Alignment);
   //Check alignment
   assert((static_cast<char*>(ptr)-static_cast<char*>(0)) % Alignment == 0);
   //Deallocate it
   managed_shm.deallocate(ptr);
   return 0;
}

[Caution]Caution

Эта функция экспериментальная, интерфейс и ABI нестабильны.

Если приложение должно выделять много буферов памяти, но оно должно самостоятельно распределять их, приложение обычно вынуждено запускать вызов<allocate()>. Сегменты управляемой памяти предлагают альтернативную функцию для пакетирования нескольких выделений в один вызов, получая буферы памяти, которые:

  • Упакованы в память (что улучшает локальность)
  • могут быть независимо размещены.

Этот метод распределения намного быстрее, чем вызов<allocate()>в петле. Недостатком является то, что сегмент должен обеспечивать смежный сегмент памяти, достаточно большой, чтобы удерживать все выделения. Сегменты управляемой памяти обеспечивают эту функциональность с помощью функций<allocate_many()>. Существует 2 типа функций<allocate_many>:

  • Распределение N буферов памяти с одинаковым размером.
  • Распределение от N буферов памяти, каждый из которых разного размера.
//!Allocates n_elements of elem_bytes bytes.
//!Throws bad_alloc on failure. chain.size() is not increased on failure.
void allocate_many(size_type elem_bytes, size_type n_elements, multiallocation_chain &chain);
//!Allocates n_elements, each one of element_lengths[i]*sizeof_element bytes.
//!Throws bad_alloc on failure. chain.size() is not increased on failure.
void allocate_many(const size_type *element_lengths, size_type n_elements, size_type sizeof_element, multiallocation_chain &chain);
//!Allocates n_elements of elem_bytes bytes.
//!Non-throwing version. chain.size() is not increased on failure.
void allocate_many(std::nothrow_t, size_type elem_bytes, size_type n_elements, multiallocation_chain &chain);
//!Allocates n_elements, each one of
//!element_lengths[i]*sizeof_element bytes.
//!Non-throwing version. chain.size() is not increased on failure.
void allocate_many(std::nothrow_t, const size_type *elem_sizes, size_type n_elements, size_type sizeof_element, multiallocation_chain &chain);
//!Deallocates all elements contained in chain.
//!Never throws.
void deallocate_many(multiallocation_chain &chain);

Вот небольшой пример, показывающий всю эту функциональность:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/move/utility_core.hpp> //boost::move
#include <cassert>//assert
#include <cstring>//std::memset
#include <new>    //std::nothrow
#include <vector> //std::vector
int main()
{
   using namespace boost::interprocess;
   typedef managed_shared_memory::multiallocation_chain multiallocation_chain;
   //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 managed_shm(create_only,"MySharedMemory", 65536);
   //Allocate 16 elements of 100 bytes in a single call. Non-throwing version.
   multiallocation_chain chain;
   managed_shm.allocate_many(std::nothrow, 100, 16, chain);
   //Check if the memory allocation was successful
   if(chain.empty()) return 1;
   //Allocated buffers
   std::vector<void*> allocated_buffers;
   //Initialize our data
   while(!chain.empty()){
      void *buf = chain.pop_front();
      allocated_buffers.push_back(buf);
      //The iterator must be incremented before overwriting memory
      //because otherwise, the iterator is invalidated.
      std::memset(buf, 0, 100);
   }
   //Now deallocate
   while(!allocated_buffers.empty()){
      managed_shm.deallocate(allocated_buffers.back());
      allocated_buffers.pop_back();
   }
   //Allocate 10 buffers of different sizes in a single call. Throwing version
   managed_shared_memory::size_type sizes[10];
   for(std::size_t i = 0; i < 10; ++i)
      sizes[i] = i*3;
   managed_shm.allocate_many(sizes, 10, 1, chain);
   managed_shm.deallocate_many(chain);
   return 0;
}

Выделение N-буферов одинакового размера улучшает производительность пулов и контейнеров узлов (например, STL-подобных списков): при вставке ряда передних итераторов в STL-подобный список функция вставки может обнаружить количество необходимых элементов и выделить в один вызов. Узлы все еще могут быть размещены.

Выделение N буферов разных размеров может быть использовано для ускорения распределения в тех случаях, когда несколько объектов всегда должны быть выделены одновременно, но размещены в разное время. Например, класс может выполнять несколько начальных распределений (некоторые данные заголовка для сетевого пакета, например) в своем конструкторе, но также и распределения буферов, которые могут быть перераспределены в будущем (данные, которые будут отправлены через сеть). Вместо того, чтобы распределять все данные независимо, конструктор может использовать<allocate_many()>для ускорения инициализации, но он все еще может распределять и расширять память элемента переменного размера.

В общем случае<allocate_many>полезно при больших значениях N. Чрезмерное использование<allocate_many>может увеличить эффективное использование памяти, поскольку оно не может повторно использовать существующие фрагменты памяти, которые могут быть доступны для некоторых элементов.

При программировании некоторых структур данных, таких как векторы, перераспределение памяти становится важным инструментом для повышения производительности. Сегменты управляемой памяти предлагают расширенную функцию перераспределения, которая предлагает:

  • Вперед расширение: Выделенный буфер может быть расширен таким образом, чтобы конец буфера перемещался дальше. Новые данные могут быть записаны между старым и новым концом.
  • Обратное расширение: Выделенный буфер может быть расширен таким образом, что начало буфера перемещается назад. Новые данные могут быть записаны между новым началом и старым началом.
  • Сокращение: Выделенный буфер может быть уменьшен так, чтобы конец буфера был перемещен назад. Память между новым и старым концом может быть повторно использована для будущих выделений.

Расширение может быть объединено с выделением нового буфера, если расширение не получает функцию с семантикой «расширять, если не выделять новый буфер».

Помимо этих функций, функция всегда возвращает реальный размер выделенного буфера, потому что много раз из-за проблем с выравниванием выделенный буфер немного больше запрашиваемого размера. Таким образом, программист может максимизировать использование памяти, используя<allocation_command>.

Вот декларация функции:

enum boost::interprocess::allocation_type
{
   //Bitwise OR (|) combinable values
   boost::interprocess::allocate_new        = ...,
   boost::interprocess::expand_fwd          = ...,
   boost::interprocess::expand_bwd          = ...,
   boost::interprocess::shrink_in_place     = ...,
   boost::interprocess::nothrow_allocation  = ...
};
template<class T>
std::pair<T *, bool>
   allocation_command( boost::interprocess::allocation_type command
                     , std::size_t limit_size
                     , size_type &prefer_in_recvd_out_size
                     , T *&reuse_ptr);

Предпосылки для функции:

  • Если команда параметров содержит значение<boost::interprocess::shrink_in_place>, она не может содержать ни одного из этих значений:<boost::interprocess::expand_fwd>,<boost::interprocess::expand_bwd>.
  • Если команда параметров содержит<boost::interprocess::expand_fwd>или<boost::interprocess::expand_bwd>, параметр<reuse_ptr>должен быть ненулевым и возвращен предыдущей функцией распределения.
  • Если команда параметров содержит значение<boost::interprocess::shrink_in_place>, параметр<limit_size>должен быть равен или больше параметра<preferred_size>.
  • Если параметр<command>содержит любое из этих значений:<boost::interprocess::expand_fwd>или<boost::interprocess::expand_bwd>, параметр<limit_size>должен быть равен или меньше параметра<preferred_size>.

Каковы последствия этой функции:

  • Если команда параметров содержит значение<boost::interprocess::shrink_in_place>, функция попытается уменьшить размер блока памяти, на который ссылается указатель<reuse_ptr>, до значения<preferred_size>, перемещающего только конец блока. Если это невозможно, он будет пытаться уменьшить размер блока памяти настолько, насколько это возможно, пока это приводит к<size(p)<=limit_size>. Об успехе сообщается только в том случае, если это приводит к<preferred_size <=size(p)>и<size(p)<=limit_size>.
  • Если параметр<command>содержит только значение<boost::interprocess::expand_fwd>(с дополнительным дополнительным<boost::interprocess::nothrow_allocation>), распределитель попытается увеличить размер блока памяти, на который ссылаются указатели, повторно перемещая только конец блока к значению<preferred_size>. Если это невозможно, он будет пытаться увеличить размер блока памяти настолько, насколько это возможно, пока это приводит к<size(p)>=limit_size>. Об успехе сообщается только в том случае, если это приводит к<limit_size <=size(p)>.
  • Если параметр<command>содержит только значение<boost::interprocess::expand_bwd>(с дополнительным дополнительным<boost::interprocess::nothrow_allocation>), распределитель будет пытаться увеличить размер блока памяти, на который ссылается указатель<reuse_ptr>, только перемещая начало блока в возвращенное новое положение<new_ptr>. Если это невозможно, он будет пытаться переместить начало блока как можно дольше, пока это приводит к<size(new_ptr)>=limit_size>. Об успехе сообщается только в том случае, если это приводит к<limit_size <=size(new_ptr)>.
  • Если параметр<command>содержит только значение<boost::interprocess::allocate_new>(с дополнительным дополнительным<boost::interprocess::nothrow_allocation>), то распределитель попытается выделить память для<preferred_size>объектов. Если это невозможно, он попытается выделить память как минимум на 318 объектов.
  • Если параметр<command>содержит только комбинацию<boost::interprocess::expand_fwd>и<boost::interprocess::allocate_new>, (с дополнительным дополнительным<boost::interprocess::nothrow_allocation>) распределитель сначала попробует прямое расширение. Если это не удастся, он попробует новое распределение.
  • Если параметр<command>содержит только комбинацию<boost::interprocess::expand_bwd>и<boost::interprocess::allocate_new>(с дополнительным дополнительным<boost::interprocess::nothrow_allocation>), то распределитель постарается сначала получить<preferred_size>объекты, используя оба метода, если это необходимо. Если это не удастся, он попытается получить<limit_size>объектов, используя оба метода, если это необходимо.
  • Если параметр<command>содержит только комбинацию<boost::interprocess::expand_fwd>и<boost::interprocess::expand_bwd>(с дополнительным дополнительным<boost::interprocess::nothrow_allocation>), распределитель сначала попробует прямое расширение. Если это не удается, он попытается получить объекты предпочтительного размера, используя обратное расширение или комбинацию прямого и обратного расширения. Если это не удастся, он попытается получить<limit_size>объекты, используя оба метода, если это необходимо.
  • Если параметр<command>содержит только комбинацию распределений_new,<boost::interprocess::expand_fwd>и<boost::interprocess::expand_bwd>, (с дополнительным дополнительным<boost::interprocess::nothrow_allocation>) распределитель сначала попробует прямое расширение. Если это не удается, он попытается получить объекты предпочтительного размера с использованием нового распределения, обратного расширения или комбинации прямого и обратного расширения. Если это не удастся, он попытается получить<limit_size>объектов, используя те же методы.
  • Аллокатор всегда записывает размер или расширенный/выделенный/урезанный блок памяти в<received_size>. При неудаче распределитель записывает в<received_size>возможный успешный<limit_size>параметр для нового вызова.

Исключение, если выполняются два условия:

  • Выделитель не может выделить/расширить/сократить память или есть ошибка в предварительных условиях.
  • Командный параметр не содержит<boost::interprocess::nothrow_allocation>.

Эта функция возвращается:

  • Адрес выделенной памяти или новый адрес расширенной памяти в качестве первого члена пары. Если команда параметров содержит<boost::interprocess::nothrow_allocation>, первый элемент будет равен 0, если распределение/расширение не удается или есть ошибка в предварительных условиях.
  • Второй член пары будет ложным, если память была выделена, истинным, если память была расширена. Если первый элемент равен 0, то второй элемент имеет неопределенное значение.

Notes:

  • Если пользователь выбирает<char>в качестве аргумента шаблона, возвращенный буфер будет соответствующим образом выровнен для удержания любого типа.
  • Если пользователь выбирает<char>в качестве аргумента шаблона и выполняется обратное расширение, хотя и правильно выровнено, возвращенный буфер может не подходить, потому что расстояние между новым началом и старым началом может не превышать тип, который пользователь хочет построить, поскольку из-за внутренних ограничений расширение может быть немного больше, чем запрошенные байты.При выполнении обратного расширения, если вы уже построили объекты в старом буфере, обязательно укажите правильно тип.

Вот небольшой пример использования<allocation_command>:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <cassert>
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;
   //Managed memory segment that allocates portions of a shared memory
   //segment with the default management algorithm
   managed_shared_memory managed_shm(create_only, "MySharedMemory", 10000*sizeof(std::size_t));
   //Allocate at least 100 bytes, 1000 bytes if possible
   managed_shared_memory::size_type min_size = 100;
   managed_shared_memory::size_type first_received_size = 1000;
   std::size_t *hint = 0;
   std::size_t *ptr = managed_shm.allocation_command<std::size_t>
      (boost::interprocess::allocate_new, min_size, first_received_size, hint);
   //Received size must be bigger than min_size
   assert(first_received_size >= min_size);
   //Get free memory
   managed_shared_memory::size_type free_memory_after_allocation = managed_shm.get_free_memory();
   //Now write the data
   for(std::size_t i = 0; i < first_received_size; ++i) ptr[i] = i;
   //Now try to triplicate the buffer. We won't admit an expansion
   //lower to the double of the original buffer.
   //This "should" be successful since no other class is allocating
   //memory from the segment
   min_size = first_received_size*2;
   managed_shared_memory::size_type expanded_size = first_received_size*3;
   std::size_t * ret = managed_shm.allocation_command
      (boost::interprocess::expand_fwd, min_size, expanded_size, ptr);
   //Check invariants
   assert(ptr != 0);
   assert(ret == ptr);
   assert(expanded_size >= first_received_size*2);
   //Get free memory and compare
   managed_shared_memory::size_type free_memory_after_expansion = managed_shm.get_free_memory();
   assert(free_memory_after_expansion < free_memory_after_allocation);
   //Write new values
   for(std::size_t i = first_received_size; i < expanded_size; ++i)  ptr[i] = i;
   //Try to shrink approximately to min_size, but the new size
   //should be smaller than min_size*2.
   //This "should" be successful since no other class is allocating
   //memory from the segment
   managed_shared_memory::size_type shrunk_size = min_size;
   ret = managed_shm.allocation_command
      (boost::interprocess::shrink_in_place, min_size*2, shrunk_size, ptr);
   //Check invariants
   assert(ptr != 0);
   assert(ret == ptr);
   assert(shrunk_size <= min_size*2);
   assert(shrunk_size >= min_size);
   //Get free memory and compare
   managed_shared_memory::size_type free_memory_after_shrinking = managed_shm.get_free_memory();
   assert(free_memory_after_shrinking > free_memory_after_expansion);
   //Deallocate the buffer
   managed_shm.deallocate(ptr);
   return 0;
}

<allocation_command>— очень мощная функция, которая может привести к значительному повышению производительности. Это особенно полезно при программировании векторных структур данных, где программист может минимизировать как количество запросов на выделение, так и потери памяти.

При отображении сегмента памяти на основе совместно используемой памяти или файлов есть возможность открыть их с помощьюopen_copy_on_writeопции. Эта опция похожа на<open_only>, но каждое изменение, которое программист делает с этим управляемым сегментом, остается закрытым для этого процесса и не переводится на базовое устройство (общая память или файл).

Базовая общая память или файл открываются только для чтения, поэтому несколько процессов могут совместно использовать начальный управляемый сегмент и вносить в него частные изменения. Если многие процессы открывают управляемый сегмент в режиме копирования в режиме записи, а не модифицированные страницы из управляемого сегмента будут совместно использоваться всеми этими процессами, при значительной экономии памяти.

Открытие управляемой совместно используемой памяти и отображенных файлов сopen_read_onlyотображает базовое устройство в памяти сатрибутами только для чтения. Это означает, что любая попытка написать эту память, создавая объекты или блокируя любой mutex, может привести к ошибке ошибки страницы (и, следовательно, прекращению программы) из ОС. Режим только для чтения открывает базовое устройство (общая память, файл ...) в режиме только для чтения и может привести к значительной экономии памяти, если несколько процессов просто хотят обработать управляемый сегмент памяти без его изменения. Режим работы только для чтения ограничен:

  • Режим только для чтения должен использоваться только из управляемых классов. Если программист получает менеджер сегмента и пытается использовать его напрямую, это может привести к нарушению доступа. Причина этого заключается в том, что менеджер сегментов размещается в базовом устройстве и ничего не говорит о режиме, в котором он был отображен в памяти.
  • Следует использовать только функции Const-члена из управляемых сегментов.
  • Кроме того, функция<find<>>не использует внутренние замки и может использоваться для поиска названных и уникальных объектов.

Вот пример, который показывает использование этих двух открытых режимов:

#include <boost/interprocess/managed_mapped_file.hpp>
#include <fstream> //std::fstream
#include <iterator>//std::distance
int main()
{
   using namespace boost::interprocess;
   //Define file names
   const char *ManagedFile  = "MyManagedFile";
   const char *ManagedFile2 = "MyManagedFile2";
   //Try to erase any previous managed segment with the same name
   file_mapping::remove(ManagedFile);
   file_mapping::remove(ManagedFile2);
   remove_file_on_destroy destroyer1(ManagedFile);
   remove_file_on_destroy destroyer2(ManagedFile2);
   {
      //Create an named integer in a managed mapped file
      managed_mapped_file managed_file(create_only, ManagedFile, 65536);
      managed_file.construct<int>("MyInt")(0u);
      //Now create a copy on write version
      managed_mapped_file managed_file_cow(open_copy_on_write, ManagedFile);
      //Erase the int and create a new one
      if(!managed_file_cow.destroy<int>("MyInt"))
         throw int(0);
      managed_file_cow.construct<int>("MyInt2");
      //Check changes
      if(managed_file_cow.find<int>("MyInt").first && !managed_file_cow.find<int>("MyInt2").first)
         throw int(0);
      //Check the original is intact
      if(!managed_file.find<int>("MyInt").first && managed_file.find<int>("MyInt2").first)
         throw int(0);
      {  //Dump the modified copy on write segment to a file
         std::fstream file(ManagedFile2, std::ios_base::out | std::ios_base::binary);
         if(!file)
            throw int(0);
       file.write(static_cast<const char *>(managed_file_cow.get_address()), (std::streamsize)managed_file_cow.get_size());
      }
      //Now open the modified file and test changes
      managed_mapped_file managed_file_cow2(open_only, ManagedFile2);
      if(managed_file_cow2.find<int>("MyInt").first && !managed_file_cow2.find<int>("MyInt2").first)
         throw int(0);
   }
   {
      //Now create a read-only version
      managed_mapped_file managed_file_ro(open_read_only, ManagedFile);
      //Check the original is intact
      if(!managed_file_ro.find<int>("MyInt").first && managed_file_ro.find<int>("MyInt2").first)
         throw int(0);
      //Check the number of named objects using the iterators
      if(std::distance(managed_file_ro.named_begin(),  managed_file_ro.named_end())  != 1 &&
         std::distance(managed_file_ro.unique_begin(), managed_file_ro.unique_end()) != 0 )
         throw int(0);
   }
   return 0;
}

Boost.Interprocess offers managed shared memory between processes using managed_shared_memory or managed_mapped_file. Two processes just map the same the memory mappable resource and read from and write to that object.

Много раз мы не хотим использовать этот подход совместной памяти, и мы предпочитаем отправлять сериализованные данные через сеть, локальные розетки или очереди сообщений. Сериализация может осуществляться черезBoost.Serializationили аналогичную библиотеку. Однако, если два процесса имеют один и тот же ABI (прикладной двоичный интерфейс), мы можем использовать одни и те же возможности построения объектов и контейнеров<managed_shared_memory>или<managed_heap_memory>для создания всей информации в одном буфере, который будет отправлен, например, через очереди сообщений. Приемник просто скопировал данные в локальный буфер, и он мог читать или изменять их напрямую, не десерализуя данные. Такой подход может быть гораздо эффективнее сложного механизма сериализации.

Приложения для сервисовBoost.Interprocessс использованием буферов необщей памяти:

  • Создание и использование совместимых с STL контейнеров и распределителей в системах, где динамическая память не рекомендуется.
  • Создание сложных, легко сериализуемых баз данных в одном буфере:
    • Обмен данными между потоками
    • Для сохранения и загрузки информации из / в файлы.
  • Дублированная информация (контейнеры, распределители и т.д.) просто копирует содержимое одного буфера в другой.
  • Отправка сложной информации и объектов/баз данных с использованием последовательной/межпроцессной/сетевой связи.

Чтобы помочь с этим управлением,Boost.Interprocessпредоставляет два полезных класса,<basic_managed_heap_memory>и<basic_managed_external_buffer>:

Иногда пользователь хочет создать простые объекты, совместимые с STL контейнеры, совместимые с STL строки и многое другое, все в одном буфере. Этот буфер может быть большим статическим буфером, вспомогательным устройством с картой памяти или любым другим пользовательским буфером.

Это позволило бы упростить сериализацию, и нам просто нужно будет скопировать буфер для дублирования всех объектов, созданных в исходном буфере, включая сложные объекты, такие как карты, списки...Boost.Interprocessпредлагает классы сегментов управляемой памяти для обработки буферов, предоставляемых пользователем, которые обеспечивают ту же функциональность, что и классы общей памяти:

//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
template <
            class CharType,
            class MemoryAlgorithm,
            template<class IndexConfig> class IndexType
         >
class basic_managed_external_buffer;
//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
//   Names are c-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer <
   char,
   rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
   flat_map_index
   >  managed_external_buffer;
//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
//   Names are wide-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer<
   wchar_t,
   rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
   flat_map_index
   >  wmanaged_external_buffer;

Чтобы использовать управляемый внешний буфер, вы должны включить следующий заголовок:

#include <boost/interprocess/managed_external_buffer.hpp>

Рассмотрим пример использования управляемого_внешнего_буфера:

#include <boost/interprocess/managed_external_buffer.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/list.hpp>
#include <cstring>
#include <boost/aligned_storage.hpp>
int main()
{
   using namespace boost::interprocess;
   //Create the static memory who will store all objects
   const int memsize = 65536;
   static boost::aligned_storage<memsize>::type static_buffer;
   //This managed memory will construct objects associated with
   //a wide string in the static buffer
   wmanaged_external_buffer objects_in_static_memory
      (create_only, &static_buffer, memsize);
   //We optimize resources to create 100 named objects in the static buffer
   objects_in_static_memory.reserve_named_objects(100);
   //Alias an integer node allocator type
   //This allocator will allocate memory inside the static buffer
   typedef allocator<int, wmanaged_external_buffer::segment_manager>
      allocator_t;
   //Alias a STL compatible list to be constructed in the static buffer
   typedef list<int, allocator_t>    MyBufferList;
   //The list must be initialized with the allocator
   //All objects created with objects_in_static_memory will
   //be stored in the static_buffer!
   MyBufferList *list = objects_in_static_memory.construct<MyBufferList>(L"MyList")
                           (objects_in_static_memory.get_segment_manager());
   //Since the allocation algorithm from wmanaged_external_buffer uses relative
   //pointers and all the pointers constructed int the static memory point
   //to objects in the same segment,  we can create another static buffer
   //from the first one and duplicate all the data.
   static boost::aligned_storage<memsize>::type static_buffer2;
   std::memcpy(&static_buffer2, &static_buffer, memsize);
   //Now open the duplicated managed memory passing the memory as argument
   wmanaged_external_buffer objects_in_static_memory2
      (open_only, &static_buffer2, memsize);
   //Check that "MyList" has been duplicated in the second buffer
   if(!objects_in_static_memory2.find<MyBufferList>(L"MyList").first)
      return 1;
   //Destroy the lists from the static buffers
   objects_in_static_memory.destroy<MyBufferList>(L"MyList");
   objects_in_static_memory2.destroy<MyBufferList>(L"MyList");
   return 0;
}

Boost.InterprocessSTL-совместимые распределители также могут использоваться для размещения STL-совместимых контейнеров в пользовательском сегменте.

<basic_managed_external_buffer>также может быть полезным для создания небольших баз данных для встраиваемых систем, ограничивающих размер используемой памяти предопределенным объемом памяти, вместо того, чтобы позволить базе данных фрагментировать кучу памяти.

Использование кучной памяти (новой/удаленной) для получения буфера, где пользователь хочет хранить все свои данные, очень распространено, поэтомуBoost.Interprocessпредоставляет некоторые специализированные классы, которые работают исключительно с кучной памятью.

Это классы:

//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
template <
            class CharType,
            class MemoryAlgorithm,
            template<class IndexConfig> class IndexType
         >
class basic_managed_heap_memory;
//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
//   Names are c-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory <
   char,
   rbtree_best_fit<null_mutex_family>,
   flat_map_index
   >  managed_heap_memory;
//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
//   Names are wide-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory<
   wchar_t,
   rbtree_best_fit<null_mutex_family>,
   flat_map_index
   >  wmanaged_heap_memory;

Чтобы использовать управляемую кучу памяти, вы должны включить следующий заголовок:

#include <boost/interprocess/managed_heap_memory.hpp>

Использование точно такое же, как<basic_managed_external_buffer>, за исключением того, что память создается самим управляемым сегментом памяти с использованием динамической (новой/удаленной) памяти.

basic_managed_heap_memoryтакже предлагает функцию<grow(std::size_textra_bytes)>, которая пытается изменить размер внутренней кучной памяти, чтобы у нас было место для большего количества объектов. Нобудьте осторожны, если память перераспределена, старый буфер будет скопирован в новый, так что все объекты будут двоичными копиями в новый буфер. Чтобы использовать эту функцию, все указатели, построенные в буфере кучи, которые указывают на объекты в буфере кучи, должны быть относительными указателями (например,<offset_ptr>). В противном случае результат не определен. Вот пример:

#include <boost/interprocess/containers/list.hpp>
#include <boost/interprocess/managed_heap_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <cstddef>
using namespace boost::interprocess;
typedef list<int, allocator<int, managed_heap_memory::segment_manager> >
   MyList;
int main ()
{
   //We will create a buffer of 1000 bytes to store a list
   managed_heap_memory heap_memory(1000);
   MyList * mylist = heap_memory.construct<MyList>("MyList")
                        (heap_memory.get_segment_manager());
   //Obtain handle, that identifies the list in the buffer
   managed_heap_memory::handle_t list_handle = heap_memory.get_handle_from_address(mylist);
   //Fill list until there is no more memory in the buffer
   try{
      while(1) {
         mylist->insert(mylist->begin(), 0);
      }
   }
   catch(const bad_alloc &){
      //memory is full
   }
   //Let's obtain the size of the list
   MyList::size_type old_size = mylist->size();
   //To make the list bigger, let's increase the heap buffer
   //in 1000 bytes more.
   heap_memory.grow(1000);
   //If memory has been reallocated, the old pointer is invalid, so
   //use previously obtained handle to find the new pointer.
   mylist = static_cast<MyList *>
               (heap_memory.get_address_from_handle(list_handle));
   //Fill list until there is no more memory in the buffer
   try{
      while(1) {
         mylist->insert(mylist->begin(), 0);
      }
   }
   catch(const bad_alloc &){
      //memory is full
   }
   //Let's obtain the new size of the list
   MyList::size_type new_size = mylist->size();
   assert(new_size > old_size);
   //Destroy list
   heap_memory.destroy_ptr(mylist);
   return 0;
}

Все сегменты управляемой памяти имеют сходные возможности (распределение памяти внутри сегмента памяти, названное конструкцией объекта...), но есть некоторые замечательные различия междууправляемой_общей_памятью,управляемой_mapped_файломиуправляемой_heap_памятью,управляемой_внешней_файлом.

  • Специализации по умолчанию для управляемой совместной памяти и отображенного использования файлов с общими процессами mutexes. Крутая память и внешний буфер по умолчанию не имеют внутренней синхронизации. Причина заключается в том, что первые два, как полагают, совместно используются между процессами (хотя файлы, отображаемые в памяти, могут использоваться только для получения постоянной базы данных объекта для процесса), тогда как последние два, как полагают, используются внутри одного процесса для создания сериализованной базы данных объекта, которая может быть отправлена через последовательные межпроцессные коммуникации (например, очереди сообщений, локальная хост-сеть).
  • Первые два создают системно-глобальный объект (общий объект памяти или файл), совместно используемый несколькими процессами, в то время как последние два являются объектами, которые не создают общесистемные ресурсы.

Чтобы увидеть полезность управляемой кучной памяти и управляемых внешних буферных классов, следующий пример показывает, как можно использовать очередь сообщений для сериализации целой базы данных, построенной в буфере памяти с использованиемBoost.Interprocess, отправить базу данных через очередь сообщений и дублировать в другом буфере:

//This test creates a in memory data-base using Interprocess machinery and
//serializes it through a message queue. Then rebuilds the data-base in
//another buffer and checks it against the original data-base
bool test_serialize_db()
{
   //Typedef data to create a Interprocess map
   typedef std::pair<const std::size_t, std::size_t> MyPair;
   typedef std::less<std::size_t>   MyLess;
   typedef node_allocator<MyPair, managed_external_buffer::segment_manager>
      node_allocator_t;
   typedef map<std::size_t,
               std::size_t,
               std::less<std::size_t>,
               node_allocator_t>
               MyMap;
   //Some constants
   const std::size_t BufferSize  = 65536;
   const std::size_t MaxMsgSize  = 100;
   //Allocate a memory buffer to hold the destiny database using vector<char>
   std::vector<char> buffer_destiny(BufferSize, 0);
   message_queue::remove(test::get_process_id_name());
   {
      //Create the message-queues
      message_queue mq1(create_only, test::get_process_id_name(), 1, MaxMsgSize);
      //Open previously created message-queue simulating other process
      message_queue mq2(open_only, test::get_process_id_name());
      //A managed heap memory to create the origin database
      managed_heap_memory db_origin(buffer_destiny.size());
      //Construct the map in the first buffer
      MyMap *map1 = db_origin.construct<MyMap>("MyMap")
                                       (MyLess(),
                                       db_origin.get_segment_manager());
      if(!map1)
         return false;
      //Fill map1 until is full
      try{
         std::size_t i = 0;
         while(1){
            (*map1)[i] = i;
            ++i;
         }
      }
      catch(boost::interprocess::bad_alloc &){}
      //Data control data sending through the message queue
      std::size_t sent = 0;
      message_queue::size_type recvd = 0;
      message_queue::size_type total_recvd = 0;
      unsigned int priority;
      //Send whole first buffer through the mq1, read it
      //through mq2 to the second buffer
      while(1){
         //Send a fragment of buffer1 through mq1
       std::size_t bytes_to_send = MaxMsgSize < (db_origin.get_size() - sent) ?
                                       MaxMsgSize : (db_origin.get_size() - sent);
         mq1.send( &static_cast<char*>(db_origin.get_address())[sent]
               , bytes_to_send
               , 0);
         sent += bytes_to_send;
         //Receive the fragment through mq2 to buffer_destiny
       mq2.receive( &buffer_destiny[total_recvd]
                , BufferSize - recvd
                  , recvd
                  , priority);
         total_recvd += recvd;
         //Check if we have received all the buffer
         if(total_recvd == BufferSize){
            break;
         }
      }
      //The buffer will contain a copy of the original database
      //so let's interpret the buffer with managed_external_buffer
      managed_external_buffer db_destiny(open_only, &buffer_destiny[0], BufferSize);
      //Let's find the map
      std::pair<MyMap *, managed_external_buffer::size_type> ret = db_destiny.find<MyMap>("MyMap");
      MyMap *map2 = ret.first;
      //Check if we have found it
      if(!map2){
         return false;
      }
      //Check if it is a single variable (not an array)
      if(ret.second != 1){
         return false;
      }
      //Now let's compare size
      if(map1->size() != map2->size()){
         return false;
      }
      //Now let's compare all db values
     MyMap::size_type num_elements = map1->size();
     for(std::size_t i = 0; i < num_elements; ++i){
         if((*map1)[i] != (*map2)[i]){
            return false;
         }
      }
      //Destroy maps from db-s
      db_origin.destroy_ptr(map1);
      db_destiny.destroy_ptr(map2);
   }
   message_queue::remove(test::get_process_id_name());
   return true;
}


PrevUpHomeNext

Статья Managed Memory Segments раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 16. Boost.Interprocess может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Chapter 16. Boost.Interprocess ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 12:44:32/0.025192975997925/0