- Boost.Interprocess允许多个进程同时使用共享内存。因为共享内存从定义上来说是进程间共享的,所以Boost.Interprocess需要支持某种同步。
- 想到同步,我们会想到C++11标准库中的类或Boost.Thread。但是这些类只能用来同步同一进程内的线程,它们不支持不同进程的同步。不过,由于两种情况下面临的挑战是一样的,所以概念也是一样的。
- 在多线程应用程序中,同步对象(如mutexes和条件变量)驻留在同一个地址空间中,因此所有线程都可以使用,而共享内存的挑战是,独立的进程需要共享这些对象。例如,如果一个进程创建了一个mutex,那么它就需要以某种方式从另一个进程中访问。
- Boost.Interprocess提供了两种同步对象:匿名对象直接存储在共享内存中,这使得它们对所有进程自动可用。命名对象由操作系统管理,不存储在共享内存中,可以通过名称从程序中引用(named_mutex)。
Example 33.12. Using a named mutex with boost::interprocess::named_mutex
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <iostream>using namespace boost::interprocess;int main()
{managed_shared_memory managed_shm{open_or_create, "shm", 1024};int *i = managed_shm.find_or_construct<int>("Integer")();named_mutex named_mtx{open_or_create, "mtx"};named_mtx.lock();++(*i);std::cout << *i << '\n';named_mtx.unlock();
}
- Example 33.12 creates and uses a named mutex using the class
boost::interprocess::named_mutex
, which is defined inboost/interprocess/sync/named_mutex.hpp
. - boost::interprocess::named_mutex的构造函数期望得到一个参数,指定是否应该创建或打开mutex,以及mutex的名称。每个知道这个名字的进程都可以打开同一个mutex。要访问共享内存中的数据,程序需要通过调用成员函数lock()来获得mutex的所有权。由于mutexes一次只能由一个进程拥有,所以另一个进程可能需要等待,直到mutex被unlock()释放。一旦一个进程拥有了一个mutex的所有权,它就拥有了对mutex所保护的资源的独占性访问权。在示例33.12中,资源是一个类型为int的变量,它被递增并写入标准输出流。
- 如果示例程序被多次启动,每个实例都会打印一个比前一个值递增1的值。由于有了mutex,不同进程之间对共享内存和变量本身的访问是同步的。
Example 33.13. Using an anonymous mutex with interprocess_mutex
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <iostream>using namespace boost::interprocess;int main()
{managed_shared_memory managed_shm{open_or_create, "shm", 1024};int *i = managed_shm.find_or_construct<int>("Integer")();interprocess_mutex *mtx =managed_shm.find_or_construct<interprocess_mutex>("mtx")();mtx->lock();++(*i);std::cout << *i << '\n';mtx->unlock();
}
- Example 33.13 uses an anonymous mutex of type
boost::interprocess::interprocess_mutex
, which is defined inboost/interprocess/sync/interprocess_mutex.hpp
. In order for the mutex to be accessible for all processes, it is stored in the shared memory. -
例33.13的行为与前一个完全相同。唯一不同的是mutex,它现在直接存储在共享内存中。这可以通过类 boost::interprocess::managed_shared_memory 中的成员函数 construct() 或 find_or_construct() 来实现。除了lock(),boost::interprocess::named_mutex和boost::interprocess::interprocess_mutex都提供了成员函数try_lock()和timed_lock()。它们的行为与标准库和Boost.Thread中的对应函数完全相同。如果需要递归互斥,Boost.Interprocess提供了两个类:boost::interprocess::named_recursive_mutex和boost::interprocess::interprocess_recursive_mutex。
-
互斥保证了对共享资源的独占访问,而条件变量则控制了谁在什么时候拥有独占访问权。一般来说,Boost.Interprocess提供的条件变量与C++11标准库和Boost.Thread提供的条件变量的工作原理类似。它们具有类似的接口,这使得这些库的用户在Boost.Interprocess中使用这些变量时,会立即感到宾至如归。
Example 33.14. Using a named condition with boost::interprocess::named_condition
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <boost/interprocess/sync/named_condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>using namespace boost::interprocess;int main()
{managed_shared_memory managed_shm{open_or_create, "shm", 1024};int *i = managed_shm.find_or_construct<int>("Integer")(0);named_mutex named_mtx{open_or_create, "mtx"};named_condition named_cnd{open_or_create, "cnd"};scoped_lock<named_mutex> 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");
}
- Example 33.14 uses a condition variable of type
boost::interprocess::named_condition
, which is defined inboost/interprocess/sync/named_condition.hpp
. Because it is a named variable, it does not need to be stored in shared memory. - 例33.14使用了一个类型为boost::interprocess::named_condition的条件变量,它定义在boost/interprocess/sync/named_condition.hpp中。因为它是一个命名变量,所以不需要存储在共享内存中。
- 应用程序使用一个 while 循环来递增一个类型为 int 的变量,该变量存储在共享内存中。虽然变量随着循环的每次迭代而递增,但它只会在每第二次迭代时被写入标准输出流--只写奇数。每当变量递增1时,条件变量命名为_cnd的成员函数wait()被调用。一个锁--在例33.14中,名为锁的变量--被传递给这个成员函数。这是基于RAII的习惯,即在构造函数中取得一个mutex的所有权,并在destructor中释放它。
- 这个锁是在while循环之前创建的,并且在整个程序执行过程中拥有mutex的所有权。然而,如果将其作为参数传递给wait(),锁就会自动释放。条件变量用于等待指示等待结束的信号。同步是由成员函数wait()和notify_all()控制的。当程序调用wait()时,相应的mutex的所有权被释放。然后,程序会一直等待,直到对同一个条件变量调用notify_all()。启动时,例33.14似乎并没有什么作用。在while循环内将变量i从0增到1后,程序通过调用wait()等待信号。为了发射信号,需要启动第二个程序实例。
- 第二个实例在进入 while 循环之前,试图取得同一个 mutex 的所有权。由于第一个实例通过调用wait()释放了mutex,所以成功了。因为变量已经被递增了一次,第二个实例执行if表达式的else分支,并将当前值写入标准输出流。然后将值递增1。现在第二个实例也调用wait()。然而,在这之前,它调用notify_all(),这确保了两个实例的正确合作。第一个实例得到通知,并试图再次获得mutex的所有权,而这个mutex仍然由第二个实例拥有。然而,由于第二个实例在调用notify_all()后立即调用wait(),它会自动释放所有权,所以第一个实例将在此时获得所有权。两个实例交替进行,递增共享内存中的变量。但是,只有一个实例将值写入标准输出流。当变量达到值10时,while循环就结束了。为了避免让另一个实例永远等待信号,循环结束后再调用一次notify_all()。在终止之前,共享内存、mutex和条件变量被销毁。
- 就像有两种类型的mutexes--一种是必须存储在共享内存中的匿名类型,另一种是命名类型--条件变量也有两种类型。例33.15是使用匿名条件变量重写的上一个例子。
Example 33.15. Using an anonymous condition with interprocess_condition
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <iostream>using namespace boost::interprocess;int main()
{managed_shared_memory managed_shm{open_or_create, "shm", 1024};int *i = managed_shm.find_or_construct<int>("Integer")(0);interprocess_mutex *mtx =managed_shm.find_or_construct<interprocess_mutex>("mtx")();interprocess_condition *cnd =managed_shm.find_or_construct<interprocess_condition>("cnd")();scoped_lock<interprocess_mutex> 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");
}
- 例33.15的工作原理和上一个完全一样,同样需要启动两次才能将int变量递增十次。除了mutexes和条件变量,Boost.Interprocess还支持semaphores和文件锁。Semaphores与条件变量类似,只是它们不区分两种状态,而是基于一个计数器。文件锁的行为类似于mutexes,只是它们用于硬盘上的文件,而不是内存中的对象。
- 就像C++11标准库和Boost.Thread区分不同类型的mutexes和锁一样,Boost.Interprocess提供了几种mutexes和锁。例如,mutexes可以是独占的,也可以是非独占的。如果多个进程需要同时读取数据,这很有帮助,因为只需要一个独占的互斥来写入数据。不同的锁类可以将RAII习语应用于单个mutexes。
- 除非使用匿名同步对象,否则名称应该是唯一的。即使mutexes和条件变量是基于不同类的对象,但对于由Boost.Interprocess封装的操作系统依赖性接口来说,这不一定成立。在Windows上,mutexes和条件变量都使用相同的操作系统函数。如果两个对象使用相同的名称,每种类型的对象都有一个,那么程序在Windows上将无法正常运行。
参考链接
- Synchronization