8.3. 托管共享内存
上一节介绍了用来创建和管理共享的 boost::interprocess::shared_memory_object
类。 实际上,由于这个类需要按单个字节的方式读写共享内存,所以这个类几乎不用。 概念上来讲,C++改善了类对象的创建并隐藏了它们存储在内存中哪里,是怎们存储的这些细节。
Boost.Interprocess 提供了一个名为“托管共享内存”的概念,通过定义在 boost/interprocess/managed_shared_memory.hpp
文件中的 boost::interprocess::managed_shared_memory
类提供。 这个类的目的是,对于需要分配到共享内存上的对象,它能够以内存申请的方式初始化,并使其自动为使用同一个共享内存的其他应用程序可用。
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <iostream>
- int main()
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- int *i = managed_shm.construct<int>("Integer")(99);
- std::cout << *i << std::endl;
- std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
- if (p.first)
- std::cout << *p.first << std::endl;
- }
上面的例子打开名为 "Highscore" 大小为1,024 字节的共享内存,如果它不存在,它会被自动地创建。
在常规的共享内存中,为了读写数据,单个字节被直接访问,托管共享内存使用诸如 construct()
函数,此函数要求一个数据类型作为模板参数,此例中声明的是 int
类型,函数缺省要求一个名称来表示在共享内存中创建的对象。 此例中使用的名称是 "Integer"。
由于 construct()
函数返回一个代理对象,为了初始化创建的对象,可以传递参数给此函数。 语法看上去像调用一个构造函数。 这就确保了对象不仅能在共享内存上创建,还能够按需要的方式初始化它。
为了访问托管共享内存上的一个特定对象,用 find()
函数。 通过传递要查找对象的名称,返回或者是一个指向这个特定对象的指针,或者是0表示给定名称的对象没有找到。
正如前面例子中所见,find()
实际返回的是 std::pair
类型的对象,first 属性提供的是指向对象的指针,那么 second 属性提供的是什么呢?
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <iostream>
- int main()
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- int *i = managed_shm.construct<int>("Integer")[10](99);
- std::cout << *i << std::endl;
- std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
- if (p.first)
- {
- std::cout << *p.first << std::endl;
- std::cout << p.second << std::endl;
- }
- }
这次,通过在 construct()
函数后面给以用方括号括住的数字10,创建了一个10个元素的 int
类型的数组。 将 second 属性写到标准输出流,同样是这个数字10
。 使用这个属性,find()
函数返回的对象能够区分是单个对象还是数组对象。 对于前者,second 的值是1,而对于后者,它的值是数组元素的个数。
请注意数组中的所有元素被初始化为数值99。 不可能每个元素初始化为不同的值。
如果给定名称的对象已经在托管的共享内存中存在,那么 construct()
将会失败。 在这种情况下,construct()
返回值是0。 如果存在的对象即使存在也可以被重用,find_or_construct()
函数可以调用,此函数返回一个指向它找到的对象的指针。 此时没有初始化动作发生。
其他可以导致 construct()
失败的情况如下例所示。
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <iostream>
- int main()
- {
- try
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- int *i = managed_shm.construct<int>("Integer")[4096](99);
- }
- catch (boost::interprocess::bad_alloc &ex)
- {
- std::cerr << ex.what() << std::endl;
- }
- }
应用程序尝试创建一个 int
类型的,包含4,096个元素的数组。 然而,共享内存只有1,024 字节,于是由于共享内存不能提供请求的内存,而抛出 boost::interprocess::bad_alloc
类型的异常。
一旦对象已经在共享内存中创建,它们可以用 destroy()
函数删除。
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <iostream>
- int main()
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- int *i = managed_shm.find_or_construct<int>("Integer")(99);
- std::cout << *i << std::endl;
- managed_shm.destroy<int>("Integer");
- std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
- std::cout << p.first << std::endl;
- }
由于它是一个参数的,要被删除对象的名称传递给 destroy()
函数。 如果需要,可以检查此函数的 bool
类型的返回值,以确定是否给定的对象被找到并成功删除。 由于对象如果被找到总是被删除,所以返回值 false
表示给定名称的对象没有找到。
除了 destroy()
函数,还提供了另外一个函数 destroy_ptr()
,它能够传递一个托管共享内存中的对象的指针,它也能用来删除数组。
由于托管内存很容易用来存储在不同应用程序之间共享的对象,那么很自然就会使用到来自C++标准模板库的容器了。 这些容器需要用 new
这种方式来分配各自需要的内存。 为了在托管共享内存上使用这些容器,这就需要更加仔细地在共享内存上分配内存。
可惜的是,许多C++标准模板库的实现并不太灵活,不能够提供 Boost.Interprocess 使用 std::string
或 std::list
的容器。 移植到 Microsoft Visual Studio 2008 的标准模板库实现就是一个例子。
为了允许开发人员可以使用这些有名的来自C++标准的容器,Boost.Interprocess 在命名空间 boost::interprocess
下,提供了它们的更灵活的实现方式。 如,boost::interprocess::string
的行为实际上对应的是 std::string
,优点是它的对象能够安全地存储在托管共享内存上。
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <boost/interprocess/allocators/allocator.hpp>
- #include <boost/interprocess/containers/string.hpp>
- #include <iostream>
- int main()
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
- typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> string;
- string *s = managed_shm.find_or_construct<string>("String")("Hello!", managed_shm.get_segment_manager());
- s->insert(5, ", world");
- std::cout << *s << std::endl;
- }
为了创建在托管共享内存上申请内存的字符串,必须为 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::vector
和 boost::interprocess::map
,分别定义在 boost/interprocess/containers/vector.hpp
和 boost/interprocess/containers/map.hpp
文件中
无论何时同一个托管共享内存被不同的应用程序访问,诸如创建,查找和销毁对象的操作是自动同步的。 如果两个应用程序尝试在托管共享内存上创建不同名称的对象,访问相应地被串行化了。 为了立刻执行多个操作而不被其他应用程序的操作打断,可以使用 atomic_func()
函数。
- #include <boost/interprocess/managed_shared_memory.hpp>
- #include <boost/bind.hpp>
- #include <iostream>
- void construct_objects(boost::interprocess::managed_shared_memory &managed_shm)
- {
- managed_shm.construct<int>("Integer")(99);
- managed_shm.construct<float>("Float")(3.14);
- }
- int main()
- {
- boost::interprocess::shared_memory_object::remove("Highscore");
- boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_or_create, "Highscore", 1024);
- managed_shm.atomic_func(boost::bind(construct_objects, boost::ref(managed_shm)));
- std::cout << *managed_shm.find<int>("Integer").first << std::endl;
- std::cout << *managed_shm.find<float>("Float").first << std::endl;
- }
atomic_func()
需要一个无参数,无返回值的函数作为它的参数。 被传递的函数将以以一种确保排他访问托管共享内存的方式被调用,但仅限于对象的创建,查找和销毁操作。 如果另一个应用程序有一个指向托管内存中对象的指针,它还是可以使用这个指针修改该对象的。
Boost.Interprocess 也可以用来同步对象的访问。 由于 Boost.Interprocess 不知道在任意一个时间点谁可以访问某个对象,所以同步需要明确的状态标志,下一节介绍这些类提供的同步方式。