• 8.3. 托管共享内存

    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 不知道在任意一个时间点谁可以访问某个对象,所以同步需要明确的状态标志,下一节介绍这些类提供的同步方式。