8.3. 托管共享内存

上一节介绍了用来创建和管理共享的 boost::interprocess::shared_memory_object 类。 实际上,由于这个类需要按单个字节的方式读写共享内存,所以这个类几乎不用。 概念上来讲,C++改善了类对象的创建并隐藏了它们存储在内存中哪里,是怎们存储的这些细节。

Boost.Interprocess 提供了一个名为“托管共享内存”的概念,通过定义在 boost/interprocess/managed_shared_memory.hpp 文件中的 boost::interprocess::managed_shared_memory 类提供。 这个类的目的是,对于需要分配到共享内存上的对象,它能够以内存申请的方式初始化,并使其自动为使用同一个共享内存的其他应用程序可用。

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::interprocess::shared_memory_object::remove("Highscore");
  7. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  8. int *i = managed_shm.construct<int>("Integer")(99);
  9. std::cout << *i << std::endl;
  10. std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  11. if (p.first)
  12. std::cout << *p.first << std::endl;
  13. }

上面的例子打开名为 "Highscore" 大小为1,024 字节的共享内存,如果它不存在,它会被自动地创建。

在常规的共享内存中,为了读写数据,单个字节被直接访问,托管共享内存使用诸如 construct() 函数,此函数要求一个数据类型作为模板参数,此例中声明的是 int 类型,函数缺省要求一个名称来表示在共享内存中创建的对象。 此例中使用的名称是 "Integer"。

由于 construct() 函数返回一个代理对象,为了初始化创建的对象,可以传递参数给此函数。 语法看上去像调用一个构造函数。 这就确保了对象不仅能在共享内存上创建,还能够按需要的方式初始化它。

为了访问托管共享内存上的一个特定对象,用 find() 函数。 通过传递要查找对象的名称,返回或者是一个指向这个特定对象的指针,或者是0表示给定名称的对象没有找到。

正如前面例子中所见,find() 实际返回的是 std::pair 类型的对象,first 属性提供的是指向对象的指针,那么 second 属性提供的是什么呢?

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::interprocess::shared_memory_object::remove("Highscore");
  7. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  8. int *i = managed_shm.construct<int>("Integer")[10](99);
  9. std::cout << *i << std::endl;
  10. std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  11. if (p.first)
  12. {
  13. std::cout << *p.first << std::endl;
  14. std::cout << p.second << std::endl;
  15. }
  16. }

这次,通过在 construct() 函数后面给以用方括号括住的数字10,创建了一个10个元素的 int 类型的数组。 将 second 属性写到标准输出流,同样是这个数字10。 使用这个属性,find() 函数返回的对象能够区分是单个对象还是数组对象。 对于前者,second 的值是1,而对于后者,它的值是数组元素的个数。

请注意数组中的所有元素被初始化为数值99。 不可能每个元素初始化为不同的值。

如果给定名称的对象已经在托管的共享内存中存在,那么 construct() 将会失败。 在这种情况下,construct() 返回值是0。 如果存在的对象即使存在也可以被重用,find_or_construct() 函数可以调用,此函数返回一个指向它找到的对象的指针。 此时没有初始化动作发生。

其他可以导致 construct() 失败的情况如下例所示。

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. try
  7. {
  8. boost::interprocess::shared_memory_object::remove("Highscore");
  9. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  10. int *i = managed_shm.construct<int>("Integer")[4096](99);
  11. }
  12. catch (boost::interprocess::bad_alloc &ex)
  13. {
  14. std::cerr << ex.what() << std::endl;
  15. }
  16. }

应用程序尝试创建一个 int 类型的,包含4,096个元素的数组。 然而,共享内存只有1,024 字节,于是由于共享内存不能提供请求的内存,而抛出 boost::interprocess::bad_alloc 类型的异常。

一旦对象已经在共享内存中创建,它们可以用 destroy() 函数删除。

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::interprocess::shared_memory_object::remove("Highscore");
  7. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  8. int *i = managed_shm.find_or_construct<int>("Integer")(99);
  9. std::cout << *i << std::endl;
  10. managed_shm.destroy<int>("Integer");
  11. std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  12. std::cout << p.first << std::endl;
  13. }

由于它是一个参数的,要被删除对象的名称传递给 destroy() 函数。 如果需要,可以检查此函数的 bool 类型的返回值,以确定是否给定的对象被找到并成功删除。 由于对象如果被找到总是被删除,所以返回值 false 表示给定名称的对象没有找到。

除了 destroy() 函数,还提供了另外一个函数 destroy_ptr(),它能够传递一个托管共享内存中的对象的指针,它也能用来删除数组。

由于托管内存很容易用来存储在不同应用程序之间共享的对象,那么很自然就会使用到来自C++标准模板库的容器了。 这些容器需要用 new 这种方式来分配各自需要的内存。 为了在托管共享内存上使用这些容器,这就需要更加仔细地在共享内存上分配内存。

可惜的是,许多C++标准模板库的实现并不太灵活,不能够提供 Boost.Interprocess 使用 std::stringstd::list 的容器。 移植到 Microsoft Visual Studio 2008 的标准模板库实现就是一个例子。

为了允许开发人员可以使用这些有名的来自C++标准的容器,Boost.Interprocess 在命名空间 boost::interprocess 下,提供了它们的更灵活的实现方式。 如,boost::interprocess::string 的行为实际上对应的是 std::string,优点是它的对象能够安全地存储在托管共享内存上。

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <boost/interprocess/allocators/allocator.hpp>
  3. #include <boost/interprocess/containers/string.hpp>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. boost::interprocess::shared_memory_object::remove("Highscore");
  9. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  10. typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
  11. typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> string;
  12. string *s = managed_shm.find_or_construct<string>("String")("Hello!", managed_shm.get_segment_manager());
  13. s->insert(5, ", world");
  14. std::cout << *s << std::endl;
  15. }

为了创建在托管共享内存上申请内存的字符串,必须为 Boost.Interprocess 提供的另外一个分配器定义对应的数据类型,而不是使用C++标准提供的缺省分配器。

为了这个目的,Boost.Interprocess 在 boost/interprocess/allocators/allocator.hpp 文件中提供了 boost::interprocess::allocator 类的定义。 使用这个类,可以创建一个分配器,此分配器的内部使用的是“托管共享内存段管理器”。 段管理器负责管理位于托管共享内存之内的内存。 使用新建的分配器,与 string 相应的数据类型被定义了。 如上面所示,它使用 boost::interprocess::basic_string 而不是 std::basic_string。 上面例子中的新数据类型简单地命名为 string,它是基于 boost::interprocess::basic_string 并经过分配器访问段管理器。 为了让通过 find_or_construct() 创建的 string 特定实例,知道哪个段管理器应该被访问,相应的段管理器的指针传递给构造函数的第二个参数。

boost::interprocess::string 一起, Boost.Interprocess 还提供了许多其他C++标准中已知的容器。 如, boost::interprocess::vectorboost::interprocess::map,分别定义在 boost/interprocess/containers/vector.hppboost/interprocess/containers/map.hpp文件中

无论何时同一个托管共享内存被不同的应用程序访问,诸如创建,查找和销毁对象的操作是自动同步的。 如果两个应用程序尝试在托管共享内存上创建不同名称的对象,访问相应地被串行化了。 为了立刻执行多个操作而不被其他应用程序的操作打断,可以使用 atomic_func() 函数。

  1. #include <boost/interprocess/managed_shared_memory.hpp>
  2. #include <boost/bind.hpp>
  3. #include <iostream>
  4.  
  5. void construct_objects(boost::interprocess::managed_shared_memory &managed_shm)
  6. {
  7. managed_shm.construct<int>("Integer")(99);
  8. managed_shm.construct<float>("Float")(3.14);
  9. }
  10.  
  11. int main()
  12. {
  13. boost::interprocess::shared_memory_object::remove("Highscore");
  14. boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
  15. managed_shm.atomic_func(boost::bind(construct_objects, boost::ref(managed_shm)));
  16. std::cout << *managed_shm.find<int>("Integer").first << std::endl;
  17. std::cout << *managed_shm.find<float>("Float").first << std::endl;
  18. }

atomic_func() 需要一个无参数,无返回值的函数作为它的参数。 被传递的函数将以以一种确保排他访问托管共享内存的方式被调用,但仅限于对象的创建,查找和销毁操作。 如果另一个应用程序有一个指向托管内存中对象的指针,它还是可以使用这个指针修改该对象的。

Boost.Interprocess 也可以用来同步对象的访问。 由于 Boost.Interprocess 不知道在任意一个时间点谁可以访问某个对象,所以同步需要明确的状态标志,下一节介绍这些类提供的同步方式。