共享内存通常是进程间通信的最快形式。它提供了一个在进程之间共享的内存区域。一个进程可以向该区域写入数据,另一个进程可以读取数据。
促进Interprocess简化了常见进程间通信和同步机制的使用,并提供了多种机制:
boost::interprocess::shared_memory_object对象用来表示共享内存,头文件为
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(1024);
std::cout << shdmem.get_name() << '\n';
offset_t size;
if (shdmem.get_size(size))
std::cout << size << '\n';
}
第一个参数指定是创建共享内存还是只打开共享内存。boost::interprocess::open_or_create表示如果共享内存已经存在,将打开共享内存;如果不存在,将创建共享内存。
打开现有共享内存假定它以前已创建。要唯一标识共享内存,需要指定一个名称。该名称由传递给的构造函数的第二个参数指定。
第三个参数确定进程如何访问共享内存。
创建boost::interprocess::shared_memory_object类型的对象后,操作系统中将存在相应的共享内存块。此内存区域的大小最初为0。要使用该区域,请调用truncate(),以字节为单位传入共享内存的大小。在示例中,共享内存提供1024字节的空间。只有使用boost::interprocess::read_ write打开共享内存时,才能调用truncate()。否则,将引发boost::interprocess::interprocess_exception类型的异常。可以重复调用truncate()来调整共享内存的大小。
创建共享内存后,可以使用get_name()和get_size()等成员函数查询共享内存的名称和大小。
因为共享内存用于在不同进程之间交换数据,所以每个进程都需要将共享内存映射到其地址空间。类boost::interprocess::mapped_region用于执行此操作。访问共享内存需要两个类(boost::interprocess::shared_memory_object
和boost::interprocess::mapped_region
)。这样做的目的是,还可以使用类boost::interprocess::mapped_region区域将其他对象映射到进程的地址空间。
#include
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(1024);
mapped_region region{shdmem, read_write};
std::cout << std::hex << region.get_address() << '\n';
std::cout << std::dec << region.get_size() << '\n';
mapped_region region2{shdmem, read_only};
std::cout << std::hex << region2.get_address() << '\n';
std::cout << std::dec << region2.get_size() << '\n';
}
boost::interprocess::mapped_region需要头文件boost/interprocess/mapped_region.hpp
第一个参数是boost::interprocess::shared_memory_object,第二个参数表示读写权限。
创建两个boost::interprocess::mapped_region类型的对象。名为Boost的共享内存被两次映射到进程的地址空间。使用成员函数get_address()
和get_size()
将映射内存区域的地址和大小写入标准输出。在这两种情况下,get_size()
都返回1024,但对于每个对象,get_address()
的返回值是不同的。
#include
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object shdmem{open_or_create, "Boost", read_write};
shdmem.truncate(1024);
mapped_region region{shdmem, read_write};
int *i1 = static_cast(region.get_address());
*i1 = 99;
mapped_region region2{shdmem, read_only};
int *i2 = static_cast(region2.get_address());
std::cout << *i2 << '\n';
}
使用映射的内存区域写入和读取数字。region将数字99写入共享内存的开头。region2然后读取共享内存中的相同位置,并将数字写入标准输出流。尽管region和region2代表进程中不同的内存区域,如前一个示例中get_address()的返回值所示,程序都会打印99,因为这两个内存区域访问相同的底层共享内存。
#include
#include
using namespace boost::interprocess;
int main()
{
bool removed = shared_memory_object::remove("Boost");
std::cout << std::boolalpha << removed << '\n';
}
要删除共享内存,boost::interprocess::shared_memory_object对象提供静态成员函数remove(),该函数将要删除的共享内存的名称作为参数。
Boost.Interprocess通过一个名为boost::interprocess::remove_shared_memory_on_destroy的类部分支持RAII习惯用法。其构造函数需要现有共享内存的名称。如果销毁此类的对象,则会在析构函数中自动删除共享内存。
如果从不调用remove(),则即使程序终止,共享内存也会继续存在。是否自动删除共享内存取决于底层操作系统。Windows和许多Unix操作系统(包括Linux)在系统重新启动后会自动删除共享内存。
Windows提供了一种特殊的共享内存,一旦使用它的最后一个进程终止,它就会自动删除。访问boost::interprocess::windows_shared_memory类,该类头文件为boost/interprocess/windows_shared_memory.hpp
#include
#include
#include
using namespace boost::interprocess;
int main()
{
windows_shared_memory shdmem{open_or_create, "Boost", read_write, 1024};
mapped_region region{shdmem, read_write};
int *i1 = static_cast(region.get_address());
*i1 = 99;
mapped_region region2{shdmem, read_only};
int *i2 = static_cast(region2.get_address());
std::cout << *i2 << '\n';
}
boost::interprocess::windows_shared_memory
不提供成员函数truncate()。而是需要将共享内存的大小作为第四个参数传递给构造函数。
boost::interprocess::shared_memory_object虽然可用于创建和管理共享内存。实际上,这个类很少使用,因为它需要程序从共享内存读写单个字节。C++风格倾向于创建类的对象,并隐藏数据存储在内存中的位置和方式的细节。
Boost.Interprocess提供boost::interprocess::managed_shared_memory来支持托管共享内存。此类允许您实例化内存位于共享内存中的对象,使这些对象自动可供访问相同共享内存的任何程序使用。头文件是:boost/interprocess/managed_shared_memory.hpp
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
int *i = managed_shm.construct("Integer")(99);
std::cout << *i << '\n';
std::pair p = managed_shm.find("Integer");
if (p.first)
std::cout << *p.first << '\n';
}
打开大小为1024字节的名为Boost的共享内存。如果共享内存不存在,它将自动创建。
在常规共享内存中,直接访问单个字节以读取或写入数据。托管共享内存使用诸如construct()之类的成员函数,该函数需要一个类型作为模板参数(在示例中,int)。成员函数需要一个名称来表示在托管共享内存中创建的对象。示例使用名称Integer。
由于construct()返回代理对象,因此可以向其传递参数以初始化创建的对象。该语法看起来像是对构造函数的调用。这确保了可以在托管共享内存中创建和初始化对象。
要访问托管共享内存中的特定对象,请使用成员函数find()。通过传递要查找的对象的名称,find()返回指向该对象的指针,如果找不到具有给定名称的对象,则返回0。
如示例所示,find()返回std::pair类型的对象。指向对象的指针first作为成员变量提供。示例显示了的内容。
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
int *i = managed_shm.construct("Integer")[10](99);
std::cout << *i << '\n';
std::pair p = managed_shm.find("Integer");
if (p.first)
{
std::cout << *p.first << '\n';
std::cout << p.second << '\n';
}
}
在示例中,通过在调用construct()后提供用方括号括起来的值10,创建了一个包含10个int类型元素的数组。使用成员变量second将相同的10写入标准输出流。由于这个成员变量,您可以判断find()返回的对象是单个对象还是数组。对于前者,second为1,而对于后者,second为数组中的元素数。
请注意,数组中的所有十个元素都用值99初始化。如果要使用不同的值初始化元素,请传递迭代器。
如果托管共享内存中已存在具有给定名称的对象,则construct()将失败。在这种情况下,construct()返回0,不进行初始化。要使用现有对象,请使用成员函数find_or_construct(),该函数返回指向现有对象的指针或创建新对象。
#include
#include
using namespace boost::interprocess;
int main()
{
try
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
int *i = managed_shm.construct("Integer")[4096](99);
}
catch (boost::interprocess::bad_alloc &ex)
{
std::cerr << ex.what() << '\n';
}
}
还有其他一些情况会导致construct()失败。示例尝试创建一个包含4096个元素的int类型数组。然而,托管共享内存仅包含1024个字节。这会导致引发boost::interprocess::bad_alloc类型的异常。
在托管共享内存中创建对象后,可以使用成员函数destroy()将其删除。
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
int *i = managed_shm.find_or_construct("Integer")(99);
std::cout << *i << '\n';
managed_shm.destroy("Integer");
std::pair p = managed_shm.find("Integer");
std::cout << p.first << '\n';
}
在示例中,要删除的对象的名称作为destroy()的唯一参数传递。可以检查bool类型的返回值,以验证是否已成功找到并删除给定对象。由于如果找到对象,将始终删除该对象,因此返回值false表示未找到具有给定名称的对象。
成员函数destroy_ptr()可用于传递指向托管共享内存中对象的指针。它还可用于删除阵列。
由于托管共享内存使在进程之间共享对象变得相当容易,因此使用标准库中的容器似乎也很自然。但是,这些容器使用new分配内存。为了在托管共享内存中使用这些容器,需要告诉它们在共享内存中分配内存。
标准库的许多实现都不够灵活,无法将std::string或std::list等容器与Boost一起使用。
要允许开发人员使用标准库中的容器,请boost::interprocess名称空间中的实现。例如,boost::interprocess::string
的行为与对应的std::
string完全相同,只是字符串可以安全地存储在托管共享内存中。
#include
#include
#include
#include
using namespace boost::interprocess;
int main()
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
typedef allocator CharAllocator;
typedef basic_string, CharAllocator> string;
string *s = managed_shm.find_or_construct("String")("Hello!",
managed_shm.get_segment_manager());
s->insert(5, ", world");
std::cout << *s << '\n';
}
要创建一个字符串,该字符串将在其所在的同一托管共享内存中分配内存,必须定义相应的类型。新字符串类型必须使用Boost.Interprocess提供的分配器,而不是标准提供的默认分配器。
为此,Boost.Interprocess提供boost::interprocess::allocator类,该类在boost/interprocess/allocators/allocator.hpp中定义。通过此类,可以创建一个分配器,该分配器在内部使用托管共享内存的段管理器。段管理器负责管理托管共享内存块中的内存。使用新创建的分配器,可以为字符串定义相应的类型。如上所述,请使用boost::interprocess::basic_string
而不是std::basic_string。
新类型string
基于boost::interprocess::basic_string,并通过其分配器访问段管理器。要让通过调用find_or_construct()创建的字符串的特定实例知道它应该访问哪个段管理器,请将指向相应段管理器的指针作为第二个参数传递给构造函数。
Boost.Interprocess 为标准库中的许多其他容器提供了实现。例如,boost::interprocess::vector和boost::interprocess::map,头文件分别为:boost/interprocess/containers/vector.hpp
和boost/interprocess/containers/map.hpp
。
请注意,Boost.Container容器支持Boost.Interprocess的要求,可放入共享内存。可以使用它们代替Boost.Interprocess中的容器。
每当从不同进程访问相同的托管共享内存时,创建、查找和销毁对象等操作都会自动同步。如果两个程序试图在托管共享内存中创建具有不同名称的对象,则会相应地序列化访问。要一次执行多个操作而不被来自不同进程的操作中断,请使用成员函数atomic_func()
#include
#include
#include
using namespace boost::interprocess;
void construct_objects(managed_shared_memory &managed_shm)
{
managed_shm.construct("Integer")(99);
managed_shm.construct("Float")(3.14);
}
int main()
{
shared_memory_object::remove("Boost");
managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
auto atomic_construct = std::bind(construct_objects,
std::ref(managed_shm));
managed_shm.atomic_func(atomic_construct);
std::cout << *managed_shm.find("Integer").first << '\n';
std::cout << *managed_shm.find("Float").first << '\n';
}
atomic_func()接受一个不接受参数且没有返回值的函数作为其单个参数。传递的函数将以确保独占访问托管共享内存的方式进行调用。但是,只有当访问托管共享内存的所有其他进程也使用atomic_func()时,才能确保独占访问。如果另一个进程有一个指向托管共享内存中某个对象的指针,它可以使用其指针访问和修改该对象。
Boost.Interprocess还可用于同步对象访问。因为Boost.Interprocess不知道谁可以在特定时间访问单个对象,需要显式处理同步。以下部分介绍为同步提供的类。
Boost.Interprocess允许多个进程同时使用共享内存。因为根据定义,共享内存在进程之间是共享的,所以Boost.Interprocess需要支持某种同步。
进程间的同步与Boost.Thread或者标准库中提供的std::mutex这类线程间同步类似。但线程间同步的类只能用于同步同一进程中的线程;它们不支持不同进程的同步。然而,由于两种情况下的挑战是相同的,因此概念也是相同的。
虽然多线程应用程序中的同步对象(如互斥量和条件变量)位于相同的地址空间中,因此所有线程都可以使用,但共享内存的挑战在于独立进程需要共享这些对象。例如,如果一个进程创建了互斥体,则需要从其他进程访问互斥体。
Boost.Interprocess 提供了两种类型的同步对象:匿名对象直接存储在共享内存中,这样所有进程都可以自动使用它们。命名对象由操作系统管理,不存储在共享内存中,可以通过名称从程序中引用。
#include
#include
#include
using namespace boost::interprocess;
int main()
{
managed_shared_memory managed_shm{open_or_create, "shm", 1024};
int *i = managed_shm.find_or_construct("Integer")();
named_mutex named_mtx{open_or_create, "mtx"};
named_mtx.lock();
++(*i);
std::cout << *i << '\n';
named_mtx.unlock();
}
使用boost::interprocess::named_mutex类创建并使用命名互斥体,该类头文件在:boost/interprocess/sync/named_mutex.hpp。
boost::interprocess::named_mutex的构造函数需要一个参数,指定是创建还是打开互斥体,以及互斥体的名称。每个知道名称的进程都可以打开相同的互斥锁。要访问共享内存中的数据,程序需要通过调用成员函数lock()获得互斥体的所有权。因为互斥锁一次只能由一个进程拥有,所以另一个进程可能需要等待,直到unlock()释放互斥锁。一旦进程获得互斥体的所有权,它就可以独占访问互斥体保护的资源。在示例中,资源是一个int类型的变量,该变量递增并写入标准输出流。
如果示例程序多次启动,则每个实例将打印一个值,该值与之前的值相比增加1。由于互斥锁,对共享内存的访问和变量本身在不同进程之间是同步的。
#include
#include
#include
using namespace boost::interprocess;
int main()
{
managed_shared_memory managed_shm{open_or_create, "shm", 1024};
int *i = managed_shm.find_or_construct("Integer")();
interprocess_mutex *mtx =
managed_shm.find_or_construct("mtx")();
mtx->lock();
++(*i);
std::cout << *i << '\n';
mtx->unlock();
}
使用boost::interprocess::interprocess_mutex类型的匿名互斥体,该互斥体在头文件boost/interprocess/sync/interprocess_mutex.hpp中定义。为了让所有进程都可以访问互斥体,互斥体存储在共享内存中。
这个示例行为与前一个完全相同。唯一的区别是互斥锁,它现在直接存储在共享内存中。这可以通过boost::interprocess::managed_shared_memory类中的成员函数construct()
或 find_or_construct()
完成。
除了lock(),boost::interprocess::named_mutex
和boost::interprocess::interprocess_mutex
都提供成员函数try_ock()和timed_ock()。它们的行为与标准库和Boost中的对应项完全相同。线如果需要递归互斥锁,请Boost.Interprocess提供两个类:boost::interprocess::named_recursive_mutex
和boost::interprocess::interprocess_recursive_mutex
。
互斥锁保证对共享资源的独占访问,而条件变量控制谁在什么时候拥有独占访问权。通常,Boost.Interprocess提供的条件变量的工作方式与C++11标准库和Boost.Thread提供的类似。它们有相似的接口,这使得这些库的用户在使用Boost.Interprocess的这些变量时立即感到宾至如归。
#include
#include
#include
#include
#include
using namespace boost::interprocess;
int main()
{
managed_shared_memory managed_shm{open_or_create, "shm", 1024};
int *i = managed_shm.find_or_construct("Integer")(0);
named_mutex named_mtx{open_or_create, "mtx"};
named_condition named_cnd{open_or_create, "cnd"};
scoped_lock lock{named_mtx};
while (*i < 10)
{
if (*i % 2 == 0)
{
++(*i);
named_cnd.notify_all();
named_cnd.wait(lock);
}
else
{
std::cout << *i << std::endl;
++(*i);
named_cnd.notify_all();
named_cnd.wait(lock);
}
}
named_cnd.notify_all();
shared_memory_object::remove("shm");
named_mutex::remove("mtx");
named_condition::remove("cnd");
}
示例使用boost::interprocess::named_condition类型的条件变量,该变量在boost/interprocess/sync/named_condition.hpp中定义。因为它是一个命名变量,所以不需要存储在共享内存中。
应用程序使用while循环递增int类型的变量,该变量存储在共享内存中。尽管变量在循环的每次迭代中都会递增,但它只会在每一次迭代中写入到标准输出流中,即只会写入奇数。
每次变量递增1,就会调用named_cnd条件变量的成员函数wait(), 名为lock的互斥体传递给wait成员函数。这是基于RAII的习惯用法,即在构造函数中获得互斥体的所有权,然后在析构函数中释放互斥体。
锁在while循环之前创建,并在整个程序执行过程中拥有互斥锁的所有权。但是,如果作为参数传递给wait(),则会自动释放锁。
条件变量用于等待指示等待结束的信号。同步由成员函数wait()和notify_all()控制。当程序调用wait()时,相应互斥锁的所有权将被释放。然后,程序将等待,直到其它进程对同一条件变量调用notify_all()。
在while循环中,变量i从0增加到1后,程序通过调用wait()等待信号。为了触发信号,需要启动程序的第二个实例。
第二个实例尝试在进入while循环之前获取同一互斥体的所有权。这会成功,因为第一个实例通过调用wait()释放互斥体。因为变量已递增一次,所以第二个实例执行if表达式的else分支,并将当前值写入标准输出流。然后该值增加1。
现在,第二个实例也调用wait()。但是,在此之前,它会调用notify_all(),以确保这两个实例正确协作。第一个实例收到通知并再次尝试获取互斥锁的所有权,该互斥锁仍由第二个实例拥有。但是,由于第二个实例在调用notify_all()之后立即调用wait(),这会自动释放所有权,因此第一个实例将在此时获得所有权。
这两个实例交替使用,递增共享内存中的变量。但是,只有一个实例将值写入标准输出流。一旦变量达到值10,while循环就完成了。为了避免另一个实例永远等待信号,在循环后再次调用notify_all()。在终止之前,共享内存、互斥锁和条件变量将被销毁。
正如有两种类型的互斥体(必须存储在共享内存中的匿名类型和命名类型)一样,也有两种类型的条件变量。下面示例使用匿名条件变量重写了前面的示例。
#include
#include
#include
#include
#include
using namespace boost::interprocess;
int main()
{
managed_shared_memory managed_shm{open_or_create, "shm", 1024};
int *i = managed_shm.find_or_construct("Integer")(0);
interprocess_mutex *mtx =
managed_shm.find_or_construct("mtx")();
interprocess_condition *cnd =
managed_shm.find_or_construct("cnd")();
scoped_lock lock{*mtx};
while (*i < 10)
{
if (*i % 2 == 0)
{
++(*i);
cnd->notify_all();
cnd->wait(lock);
}
else
{
std::cout << *i << std::endl;
++(*i);
cnd->notify_all();
cnd->wait(lock);
}
}
cnd->notify_all();
shared_memory_object::remove("shm");
}
这个示例的工作方式与前一个完全相同,还需要启动两次才能将int变量递增十倍。
除了互斥量和条件变量之外,Boost.Interprocess还支持信号量和文件锁。信号量与条件变量相似,只是它们不区分两种状态;相反,它们基于计数器。文件锁的行为类似于互斥锁,只是它们用于硬盘上的文件,而不是内存中的对象。
与C++11标准库和Boost.Thread区分不同类型的互斥锁和锁一样,Boost.Interprocess提供多个互斥锁和锁。例如,互斥锁可以是独占的或非独占的。如果多个进程需要同时读取数据,这会很有帮助,因为只有写入数据时才需要专用互斥锁。不同的锁类可用于将RAII习惯用法应用于各个互斥锁。
除非使用匿名同步对象,否则名称应唯一。尽管互斥量和条件变量是基于不同类的对象,但对于Boost包装的依赖于操作系统的接口,这可能不一定成立。进程间。在Windows上,相同的操作系统函数用于互斥量和条件变量。如果两个对象(每种类型一个)使用相同的名称,程序将无法在Windows上正常运行。