Boost.Interprocess使用手册翻译之六:同步机制(Synchronization mechanisms)

六.          同步机制

同步机制概述

互斥量

条件变量

信号量

升级互斥量

通过移动语义转移锁

文件锁

消息队列

 

同步机制概述

具名和匿名同步机制

同步机制类型

如前所述,如果对内存的访问不能有效的同步,则通过内存映射文件或共享内存对象在进程间共享内存的能力就不是非常有用了。与需要在进程间共享堆栈和全局变量的进程间同步机制遇到的问题一样,访问这些资源一般需要使用互斥量或条件变量进行同步。Boost.Threads在同一进程的线程间实现了这些同步机制。Boost.Interprocess则在不同的进程间实现了类似的机制。

 

具名和匿名同步机制

Boost.Interprocess提供了两种同步对象:

  • 具名实用工具:若两个进程想创建此种类型的对象,它们必须使用相同的名字创建一个对象。这与创建或打开文件类似:一个进程使用采用filename的fstream创建一个文件,另一个进程使用另一个采用同样的名字为参数的fstream打开文件。虽然每个进程都使用一个不同的对象来访问资源,但这些进程均使用相同的底层资源。
  • 匿名实用工具:因为这些实用工具没有名字,因此两个进程必须通过共享内存或内存映射文件共享同一个对象。这与传统的线程间同步对象类似:所有进程共享同一对象。与全局变量和堆栈内存在同一进程的线程间共享的线程同步不同,在不同进程的两个线程间共享对象仅能通过映射区域进行,该映射区域映射了相同的可映射资源(例如,共享内存或内存映射文件)。

以上两种方式各有优缺点:

  • 具名实用工具在处理简单的同步任务时要简单些,因为进程不需要创建共享内存区域以及构建同步机制。
  • 当使用内存映射对象获得同步工具的自动持久化属性时,匿名实用工具可以被序列化至磁盘。你可以在内存映射文件上构建一个同步工具,重启系统,再次映射此文件,从而再次使用此同步化工具。这在具名实用工具的方式下是不行的。

具名和匿名实用工具在接口上的不同主要反映在构造函数上。一般而言,匿名实用工具仅有一个构造函数,而具名实用工具有多个构造函数,它们的第一个参数是一个需要创建或打开底层资源的特殊类型:

using namespace boost::interprocess;
 
//Create the synchronization utility. If it previously
//exists, throws an error
NamedUtility(create_only, ...)
 
//Open the synchronization utility. If it does not previously
//exist, it's created.
NamedUtility(open_or_create, ...)
 
//Open the synchronization utility. If it does not previously
//exist, throws an error.
NamedUtility(open_only, ...)
另一方面,匿名同步实用工具仅能被创建,并且进程间必须使用创建此实用工具的其他同步机制进行同步:
using namespace boost::interprocess;
 
//Create the synchronization utility.
AnonymousUtility(...)

 

同步机制类型

除了其具名/匿名特性,Boost.Interprocess提供了一下同步实用工具:

  • 互斥量(具名/匿名)
  • 条件变量(具名/匿名)
  • 信号量(具名/匿名)
  • 升级互斥类型
  • 文件锁

 

互斥量

什么是一个互斥量

互斥量操作

Boost.Interprocess互斥量类型和头文件

局部锁

匿名互斥量示例

具名互斥量示例

 

什么是一个互斥量

互斥量代表相互排斥并且它是进程间不同的最基本形式。互斥量保证仅有一个线程能够锁定一个给定的互斥量。如果一个代码片段被一个互斥量锁定或解锁,它保证一次仅有一个线程执行此段代码。当这个线程解锁了互斥量,另一个线程方可进入那段代码区域:

//The mutex has been previously constructed
 
lock_the_mutex();
 
//This code will be executed only by one thread
//at a time.
 
unlock_the_mutex();

一个互斥量可以是递归非递归的:

  • 递归互斥量可以被同一个线程锁多次。为了完全解锁互斥量,此线程必须解锁同样的次数。
  • 非递归互斥量不能被同一个线程锁多次。如果一个互斥量被一个线程锁了两次,结果是未定义的,可能会抛出一个错误或是线程被死锁。

 

互斥量操作

所有Boost.Interprocess的互斥量类型均有如下操作:

void lock()

效果:调用线程尝试获取互斥量的所有权,并且如果其他线程已经拥有了此互斥量的所有权,它等待直到能获得所有权为止。如果一个线程拥有了此互斥量的所有权,此互斥量必须被同一个线程解锁。如果互斥量支持递归锁定,则互斥量必须解锁同样的次数。

抛出:interprocess_exception错误。

bool try_lock()

效果:调用线程尝试获取互斥量的所有权,并且如果其他线程已经拥有了此互斥量的所有权,函数立刻返回。如果互斥量支持递归锁定,则互斥量必须解锁同样的次数。

返回:如果线程获取了互斥量的所有权,返回true;如果其他线程已经拥有了此互斥量的所有权,返回false。

抛出:interprocess_exception错误。

booltimed_lock(const boost::posix_time::ptime &abs_time)

效果:调用线程尝试获取互斥量的独占权限直到达到指定的时间。如果互斥量支持递归锁定,则互斥量必须解锁同样的次数。

返回:如果线程获取了互斥量的所有权,返回true,如果超时,返回false。

抛出:interprocess_exception错误。

void unlock()

前提条件:线程必须已经拥有了互斥量的独占权。

效果:调用线程释放互斥量的独占权。如果互斥量支持递归锁定,则互斥量必须解锁同样的次数。

抛出:一个派生自interprocess_exception的异常。

 

Boost.Interprocess互斥量类型和头文件

Boost.Interprocess提供了如下互斥量类型:

 
#include 
  • interprocess_mutex: 一个非递归的、匿名的互斥量,它能够被置于共享内存和内存映射文件中。
 
#include 
  • interprocess_recursive_mutex:一个递归的、匿名的互斥量,它能够被置于共享内存和内存映射文件中。
#include 
  • named_mutex:一个非递归的、具名的互斥量。
#include 
  • named_recursive_mutex:一个递归的、具名的互斥量。

局部锁

当进程已经完成了读写数据后及时解锁互斥量,这是非常重要的。这一点在遇到异常时,是比较困难的。因此,互斥量一般是带局部锁使用,局部锁能保证互斥量一直能被解锁,哪怕有异常发生。为使用一个局部锁,仅需包含:

#include 

基本上,一个局部锁在其析构函数中调用 unlock(),因此一个互斥量在异常发生时,总能被解锁。局部锁有许多构造函数用于lock, try_lock, timed_lock一个互斥量或者不锁定。

using namespace boost::interprocess;
 
//Let's create any mutex type:
MutexType mutex;
 
{
   //This will lock the mutex
   scoped_lock lock(mutex);
 
   //Some code
 
   //The mutex will be unlocked here
}
 
{
   //This will try_lock the mutex
   scoped_lock lock(mutex, try_to_lock);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
 
   //If the mutex was locked it will be unlocked
}
 
{
   boost::posix_time::ptime abs_time = ...
 
   //This will timed_lock the mutex
   scoped_lock lock(mutex, abs_time);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
 
   //If the mutex was locked it will be unlocked
}

更多详情,参考scoped_lock'sreference.

 

匿名互斥量示例

想象一下两个进程需要写入跟踪数据至一个建立在共享内存上的循环缓冲区。每个进程需要获得循环缓冲区的独占访问,写跟踪数据然后继续。

为了保护循环缓冲区,我们可以存储一个进程共享互斥量在循环缓冲区上。每个进程将在写数据前锁此互斥量,并且在结束写跟踪数据后将写一个标记(头文件doc_anonymous_mutex_shared_data.hpp):

#include 
 
struct shared_memory_log
{
   enum { NumItems = 100 };
   enum { LineSize = 100 };
 
   shared_memory_log()
      :  current_line(0)
      ,  end_a(false)
      ,  end_b(false)
   {}
 
   //Mutex to protect access to the queue
   boost::interprocess::interprocess_mutex mutex;
 
   //Items to fill
   char   items[NumItems][LineSize];
   int    current_line;
   bool   end_a;
   bool   end_b;
};

这是进程的主要过程。创建共享内存,构建循环缓冲区,然后开始写跟踪数据:

#include 
#include 
#include 
#include "doc_anonymous_mutex_shared_data.hpp"
#include 
#include 
 
using namespace boost::interprocess;
 
int main ()
{
   try{
      //Remove shared memory on construction and destruction
      struct shm_remove
      {
         shm_remove() { shared_memory_object::remove("MySharedMemory"); }
         ~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
      } remover;
 
      //Create a shared memory object.
      shared_memory_object shm
         (create_only               //only create
         ,"MySharedMemory"          //name
         ,read_write   //read-write mode
         );
 
      //Set size
      shm.truncate(sizeof(shared_memory_log));
 
      //Map the whole shared memory in this process
      mapped_region region
         (shm                       //What to map
         ,read_write   //Map it as read-write
         );
 
      //Get the address of the mapped region
      void * addr       = region.get_address();
 
      //Construct the shared structure in memory
      shared_memory_log * data = new (addr) shared_memory_log;
 
      //Write some logs
      for(int i = 0; i < shared_memory_log::NumItems; ++i){
         //Lock the mutex
         scoped_lock lock(data->mutex);
         std::sprintf(data->items[(data->current_line++) % shared_memory_log::NumItems]
                  ,"%s_%d", "process_a", i);
         if(i == (shared_memory_log::NumItems-1))
            data->end_a = true;
         //Mutex is released here
      }
 
      //Wait until the other process ends
      while(1){
         scoped_lock lock(data->mutex);
         if(data->end_b)
            break;
      }
   }
   catch(interprocess_exception &ex){
      std::cout << ex.what() << std::endl;
      return 1;
   }
   return 0;
}

第二个进程打开共享内存,获取对循环缓冲区的访问,然后开始写跟踪数据:

#include 
#include 
#include 
#include "doc_anonymous_mutex_shared_data.hpp"
#include 
#include 
 
using namespace boost::interprocess;
 
int main ()
{
   //Remove shared memory on destruction
   struct shm_remove
   {
      ~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
   } remover;
 
   //Open the shared memory object.
   shared_memory_object shm
      (open_only                    //only create
      ,"MySharedMemory"              //name
      ,read_write  //read-write mode
      );
 
   //Map the whole shared memory in this process
   mapped_region region
      (shm                       //What to map
      ,read_write //Map it as read-write
      );
 
   //Get the address of the mapped region
   void * addr       = region.get_address();
 
   //Construct the shared structure in memory
   shared_memory_log * data = static_cast(addr);
 
   //Write some logs
   for(int i = 0; i < 100; ++i){
      //Lock the mutex
      scoped_lock lock(data->mutex);
      std::sprintf(data->items[(data->current_line++) % shared_memory_log::NumItems]
               ,"%s_%d", "process_a", i);
      if(i == (shared_memory_log::NumItems-1))
         data->end_b = true;
      //Mutex is released here
   }
 
   //Wait until the other process ends
   while(1){
      scoped_lock lock(data->mutex);
      if(data->end_a)
         break;
   }
   return 0;
}

如我们所看到的,一个互斥量可用于保护数据,但是没有向另一个进程发事件通知。基于此,我们需要一个条件变量,我们将在下一节阐述它。

 

具名互斥量示例

现在想象一个两个进程想写一份跟踪数据至一个文件中。首先,它们写它们的名字,然后它们写此消息。因为操作系统能够在任意时刻中断一个进程,我们可能会在两个进程中混淆此消息的部分,因此我们需要一种原子写整个消息至文件中的方法。为达此目的,我们可以使用一个具名互斥量以便每个进程在写数据前锁定此互斥量:

#include 
#include 
#include 
#include 
#include 
 
 
int main ()
{
   using namespace boost::interprocess;
   try{
      struct file_remove
      {
         file_remove() { std::remove("file_name"); }
         ~file_remove(){ std::remove("file_name"); }
      } file_remover;
      struct mutex_remove
      {
         mutex_remove() { named_mutex::remove("fstream_named_mutex"); }
         ~mutex_remove(){ named_mutex::remove("fstream_named_mutex"); }
      } remover;
 
      //Open or create the named mutex
      named_mutex mutex(open_or_create, "fstream_named_mutex");
 
      std::ofstream file("file_name");
 
      for(int i = 0; i < 10; ++i){
 
         //Do some operations...
 
         //Write to file atomically
         scoped_lock lock(mutex);
         file << "Process name, ";
         file << "This is iteration #" << i;
         file << std::endl;
      }
   }
   catch(interprocess_exception &ex){
      std::cout << ex.what() << std::endl;
      return 1;
   }
   return 0;
}

条件变量

什么是一个条件变量

Boost.Interprocess条件变量类型和头文件

匿名条件变量示例

 

什么是一个条件变量

在之前的例子中,互斥量被用来锁定,但直到条件变量出现前,我们并不能有效使用它。一个条件变量可以做两件事情:

  • 等:线程被阻塞直到另一个线程通知它继续,因为导致等待的条件已经消失了。
  • 通知:线程发送一个信号至一个阻塞的线程或者至所有阻塞的线程,告诉它们导致它们等待的条件已经消失了。

在条件变量中等待总是会关联一个互斥量。在等待条件之前,互斥量需要预先被锁定。当在条件变量中等待时,线程解锁互斥量然后原子等待。

当线程从等待函数(例如可能由于一个信号或超时)返回后,互斥量对象再次被锁定。

 

Boost.Interprocess条件变量类型和头文件

Boost.Interprocess提供如下的条件类型:

#include 
  • interprocess_condition:一个匿名条件变量,它位于共享内存或内存映射空间被用于boost::interprocess::interprocess_mutex。
#include 
  • named_condition:一个具名条件变量被用于named_mutex。

具名条件与匿名条件类似,但它们结合具名互斥量使用。有时,我们不希望存储带同步数据的同步对象:

  • 我们想采用同样的数据改变同步方式(从进程间变为进程内,或不适用任何同步方式)。存储带共享数据的进程间共享匿名同步对象将禁止这样做。
  • 我们想通过网络或其他通信方式发送同步数据。发送进程共享的同步对象没有任何意义。

 

匿名条件变量示例

想象一下,一个进程写一份跟踪数据至简单共享内存缓冲区,另一个进程一个接一个打印出来。第一个进程写跟踪数据,然后等待直到另一个进程打印出这份数据。为达到此目的,我们可以使用两个条件变量:第一个用于阻塞发送者直到第二个进程打印出此消息,第二个用于阻塞接收者直到缓冲区中有数据供打印。

共享内存跟踪数据缓冲区(doc_anonymous_condition_shared_data.hpp):

#include 
#include 
 
struct trace_queue
{
   enum { LineSize = 100 };
 
   trace_queue()
      :  message_in(false)
   {}
 
   //Mutex to protect access to the queue
   boost::interprocess::interprocess_mutex      mutex;
 
   //Condition to wait when the queue is empty
   boost::interprocess::interprocess_condition  cond_empty;
 
   //Condition to wait when the queue is full
   boost::interprocess::interprocess_condition  cond_full;
 
   //Items to fill
   char   items[LineSize];
 
   //Is there any message
   bool message_in;
};

这是进程的主要过程。建立共享内存、放置缓冲区在其上然后开始一个接一个写消息直到它写了一个"last message"表明没有更多消息供打印:

#include 
#include 
#include 
#include 
#include 
#include "doc_anonymous_condition_shared_data.hpp"
 
using namespace boost::interprocess;
 
int main ()
{
 
   //Erase previous shared memory and schedule erasure on exit
   struct shm_remove
   {
      shm_remove() { shared_memory_object::remove("MySharedMemory"); }
      ~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
   } remover;
 
   //Create a shared memory object.
   shared_memory_object shm
      (create_only               //only create
      ,"MySharedMemory"           //name
      ,read_write                //read-write mode
      );
   try{
      //Set size
      shm.truncate(sizeof(trace_queue));
 
      //Map the whole shared memory in this process
      mapped_region region
         (shm                       //What to map
         ,read_write //Map it as read-write
         );
 
      //Get the address of the mapped region
      void * addr       = region.get_address();
 
      //Construct the shared structure in memory
      trace_queue * data = new (addr) trace_queue;
 
      const int NumMsg = 100;
 
      for(int i = 0; i < NumMsg; ++i){
         scoped_lock lock(data->mutex);
         if(data->message_in){
            data->cond_full.wait(lock);
         }
         if(i == (NumMsg-1))
            std::sprintf(data->items, "%s", "last message");
         else
            std::sprintf(data->items, "%s_%d", "my_trace", i);
 
         //Notify to the other process that there is a message
         data->cond_empty.notify_one();
 
         //Mark message buffer as full
         data->message_in = true;
      }
   }
   catch(interprocess_exception &ex){
      std::cout << ex.what() << std::endl;
      return 1;
   }
 
   return 0;
}

第二个进程打开共享内存,然后打印每个消息直到 "last message" 消息:

#include 
#include 
#include 
#include 
#include 
#include "doc_anonymous_condition_shared_data.hpp"
 
using namespace boost::interprocess;
 
int main ()
{
   //Create a shared memory object.
   shared_memory_object shm
      (open_only                    //only create
      ,"MySharedMemory"              //name
      ,read_write                   //read-write mode
      );
 
   try{
      //Map the whole shared memory in this process
      mapped_region region
         (shm                       //What to map
         ,read_write //Map it as read-write
         );
 
      //Get the address of the mapped region
      void * addr       = region.get_address();
 
      //Obtain a pointer to the shared structure
      trace_queue * data = static_cast(addr);
 
      //Print messages until the other process marks the end
      bool end_loop = false;
      do{
         scoped_lock lock(data->mutex);
         if(!data->message_in){
            data->cond_empty.wait(lock);
         }
         if(std::strcmp(data->items, "last message") == 0){
            end_loop = true;
         }
         else{
            //Print the message
            std::cout << data->items << std::endl;
            //Notify the other process that the buffer is empty
            data->message_in = false;
            data->cond_full.notify_one();
         }
      }
      while(!end_loop);
   }
   catch(interprocess_exception &ex){
      std::cout << ex.what() << std::endl;
      return 1;
   }
 
   return 0;
}

采用条件变量,如果一个线程不能继续工作,它能够阻塞,并且当遇到可以继续的条件时,另一个线程能够唤醒它。

 

信号量

什么是一个信号量

Boost.Interprocess信号量类型和头文件

匿名信号量示例

 

什么是一个信号量

信号量是一种基于内部计数的进程间同步机制,它提供了两种基本操作:

  • 等待:测试信号量计数值,如果值小于等于0,则等待。否则,减1信号量计数值。
  • 委派(Post):增1信号量计数值。如果有进程被阻塞了,则其中之一被唤醒。

如果信号量计数值被初始化为1,则等待操作与互斥锁等价,委派操作与互斥解锁等价。这种类型的信号量被称为二进制信号量

尽管信号量可以像互斥量一样使用,它有一个独特的特性:与互斥量不同,委派操作不需要由执行等待操作的那个相同的线程/进程来执行。

 

Boost.Interprocess信号量类型和头文件

Boost.Interprocess提供如下类型的信号量:

#include 
  • interprocess_semaphore: 一种匿名信号量,它能被置于共享内存或内存映射文件中。
#include 
  • named_semaphore:一种具名信号量。

 

匿名信号量示例

我们将在共享内存中实现一个整型数组,此数组将被用来从一个进程向另一个进程传递数据。第一个进程将向数组内写一些整数,并且如果数组满了,进程将阻塞。

第二个进程将拷贝传输过来的数据至其自身的buffer中,如果buffer中没有新数据了,则此进程阻塞。

以下是共享整型数组(doc_anonymous_semaphore_shared_data.hpp):

#include 
 
struct shared_memory_buffer
{
   enum { NumItems = 10 };
 
   shared_memory_buffer()
      : mutex(1), nempty(NumItems), nstored(0)
   {}
 
   //Semaphores to protect and synchronize access
   boost::interprocess::interprocess_semaphore
      mutex, nempty, nstored;
 
   //Items to fill
   int items[NumItems];
};

以下是主进程。创建共享内存、放置整型数组至共享内存上,然后开始一个个填充整数,如果数组满了,则阻塞。

#include 
#include 
#include 
#include "doc_anonymous_semaphore_shared_data.hpp"
 
using namespace boost::interprocess;
 
int main ()
{
   //Remove shared memory on construction and destruction
   struct shm_remove
   {
      shm_remove() { shared_memory_object::remove("MySharedMemory"); }
      ~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
   } remover;
 
   //Create a shared memory object.
   shared_memory_object shm
      (create_only                  //only create
      ,"MySharedMemory"              //name
      ,read_write  //read-write mode
      );
 
   //Set size
   shm.truncate(sizeof(shared_memory_buffer));
 
   //Map the whole shared memory in this process
   mapped_region region
      (shm                       //What to map
      ,read_write //Map it as read-write
      );
 
   //Get the address of the mapped region
   void * addr       = region.get_address();
 
   //Construct the shared structure in memory
   shared_memory_buffer * data = new (addr) shared_memory_buffer;
 
   const int NumMsg = 100;
 
   //Insert data in the array
   for(int i = 0; i < NumMsg; ++i){
      data->nempty.wait();
      data->mutex.wait();
      data->items[i % shared_memory_buffer::NumItems] = i;
      data->mutex.post();
      data->nstored.post();
   }
 
   return 0;
}

第二个进程打开共享内存,然后拷贝接收到的整数至其自身的共享内存:

#include 
#include 
#include 
#include "doc_anonymous_semaphore_shared_data.hpp"
 
using namespace boost::interprocess;
 
int main ()
{
   //Remove shared memory on destruction
   struct shm_remove
   {
      ~shm_remove(){ shared_memory_object::remove("MySharedMemory"); }
   } remover;
 
   //Create a shared memory object.
   shared_memory_object shm
      (open_only                    //only create
      ,"MySharedMemory"              //name
      ,read_write  //read-write mode
      );
 
   //Map the whole shared memory in this process
   mapped_region region
      (shm                       //What to map
      ,read_write //Map it as read-write
      );
 
   //Get the address of the mapped region
   void * addr       = region.get_address();
 
   //Obtain the shared structure
   shared_memory_buffer * data = static_cast(addr);
 
   const int NumMsg = 100;
 
   int extracted_data [NumMsg];
 
   //Extract the data
   for(int i = 0; i < NumMsg; ++i){
      data->nstored.wait();
      data->mutex.wait();
      extracted_data[i] = data->items[i % shared_memory_buffer::NumItems];
      data->mutex.post();
      data->nempty.post();
   }
   return 0;
}

同样的进程间通信也可以采用条件变量和互斥量来完成,但这几个同步模式中,信号量比互斥量/条件变量组合更高效。

 

升级互斥量

什么是一个升级互斥量

升级互斥量的操作

Boost.Interprocess升级互斥量类型和头文件

共享锁和升级锁

 

什么是一个升级互斥量

升级互斥量是一种特殊的互斥量,它比普通的互斥量提供了更多的锁定可能性。有时,我们能够区分读数据和修改数据。如果仅有某些线程需要修改数据,并且一个普通的互斥量被用来保护数据被并行访问,并发性是相当有限的:两个仅读取数据的线程会被序列化,而不是被并发执行。

如果我们允许对仅读取数据的线程的并发访问,但是我们避免对读/写线程的并发访问,我们就能够提升性能。这在如下应用中尤其明显:当读数据比修改数据更频繁并且同步数据读取的代码需要一定的时间来执行。采用升级互斥量,我们能够获得3种锁类型:

  • 独占锁:与普通互斥量类似。如果一个线程获取了一个独占锁,其他任何线程都不能获取此锁(互斥或其他方式)直到解锁。如果某一线程有一个共享或升级锁,则另一个企图获取独占锁的线程会阻塞。这种锁将被会修改数据的线程所拥有。
  • 共享锁:如果一个线程获取了一个共享锁,其他线程也可以获取一个共享锁或升级锁。如果任一线程获取了独占锁,则另一企图获取共享锁的线程会被阻塞。这种锁被仅需要读取数据的线程执行。
  • 升级锁:获取一个升级锁与获取一个特权共享锁类似。如果一个进程获取了一个升级锁,另外的线程可以获取一个共享锁。如果任一线程已经获取了独占锁或升级锁,则其他企图获取升级锁的线程会被阻塞。当其他已经获取了共享锁的线程释放了此锁,一个已经获取一个升级锁的线程被保证能够原子获取一个独占锁。它被用于如下线程中:此线程可能需要修改数据,但通常情况下它仅需要读取数据。这种线程获取升级锁,其他线程能获取共享锁。如果升级的线程读数据并且它需要修改数据时,此线程能够被提升至获取独占锁:当所有共享线程已经释放了共享锁时,升级锁被原子提升至独占锁。这个新被提升的线程能够修改数据,并且能够保证在过渡时,没有其他线程对数据修改。仅仅一个线程能获取升级(特权读)锁。

总结一下:

表 12.5. 锁定的可能性

如果一个线程已经获取…

其他线程能够获取…

共享锁

许多共享锁和一个升级锁

升级锁

许多共享锁

独占锁

一个已经获取了一个锁的线程能够尝试原子获取另一类型的锁。所有锁的转换不保证能成功。尽管一个转换能保证成功,一些转换将阻塞线程直到其他线程释放了共享锁。原子操作意味着没有其他线程在转换中能够获得一个升级或独占锁,因此数据能够保证未被修改:

表 12.6. 转换的可能性

如果一个线程已经获取…

它能原子释放之前的锁,并且…

共享锁

尝试立刻获取(不保证成功)独占锁,如果没有其他线程占有互斥或升级锁

共享锁

尝试立刻获取(不保证成功)升级锁,如果没有其他线程占有互斥或升级锁

升级锁

当所有共享锁被释放后,获取独占锁

升级锁

立刻获取共享锁

独占锁

立刻获取升级锁

独占锁

立刻获取共享锁

正如我们所看到的,升级互斥量是一个强大的同步工具,它能够提升并发性能。然后,如果大多数情况下我们都需要修改数据,或同步代码片段非常短,则使用普通互斥量效率更高,因为它具有较小的开销。当同步代码片段比较大并且读比写多时,升级锁才能发挥出它的光芒。

 

升级互斥量的操作

独占锁定

共享锁定

升级锁定

降级

升级

所有Boost.Interprocess中的升级互斥类型均可以执行如下操作:

独占锁定

void lock()

效果:调用者线程尝试获取互斥量的独占所有权,如果另一个线程已经有这个互斥量的独占、共享或升级所有权,则线程等待直到它获取所有权为止。

抛出:interprocess_exception异常。

bool try_lock()

效果:调用者线程尝试获取互斥量的独占所有权而不等待。如果没有其他线程具有此互斥量的独占、共享或升级所有权,则获取成功。

返回:如果能立即获得独占所有权,则返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

bool timed_lock(constboost::posix_time::ptime &abs_time)

效果:调用者线程尝试获取互斥量的独占所有权直到没有其他线程具有此互斥量的独占、共享或升级所有权或者是达到abs_time。

返回:如果获得独占所有权,则返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

void unlock()

前提条件:线程必须有互斥量的独占所有权。

效果:调用者线程释放互斥量的独占所有权。

抛出:一个派生自interprocess_exception的异常。

 

共享锁定

void lock_sharable()

效果:调用者线程尝试获取互斥量的共享所有权,并且如果另一个线程已经有这个互斥量的独占或升级所有权,则线程等待直到它获取所有权为止。

抛出:interprocess_exception异常。

bool try_lock_sharable()

效果:调用者线程尝试获取互斥量的共享所有权而不等待。如果没有其他线程具有此互斥量的独占或升级所有权,则获取成功。

返回:如果能立即获得独占所有权,则返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

bool timed_lock_sharable(constboost::posix_time::ptime &abs_time)

效果:调用者线程尝试获取互斥量的共享所有权直到没有其他线程具有此互斥量的独占或升级所有权或者是达到abs_time。

返回:如果获得共享所有权,则返回true。否则返回false。

抛出:interprocess_exception异常。

void unlock_sharable()

前提条件:线程必须有互斥量的共享所有权。

效果:调用者线程释放互斥量的共享所有权。

抛出:一个派生自interprocess_exception的异常。

 

升级锁定

void lock_upgradable()

效果:调用者线程尝试获取互斥量的升级所有权,并且如果另一个线程已经有这个互斥量的独占或升级所有权,则线程等待直到它获取所有权为止。

抛出:interprocess_exception异常。

bool try_lock_upgradable ()

效果:调用者线程尝试获取互斥量的升级所有权而不等待。如果没有其他线程具有此互斥量的独占或升级所有权,则获取成功。

返回:如果能立即获得升级所有权,则返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

booltimed_lock_upgradable(const boost::posix_time::ptime &abs_time)

效果:调用者线程尝试获取互斥量的升级所有权直到没有其他线程具有此互斥量的独占或升级所有权或者是达到abs_time。

返回:如果获得升级所有权,则返回true。否则返回false。

抛出:interprocess_exception异常。

void unlock_upgradable()

前提条件:线程必须有互斥量的升级所有权。

效果:调用者线程释放互斥量的升级所有权。

抛出:一个派生自interprocess_exception的异常。

 

降级

voidunlock_and_lock_upgradable()

前提条件:线程必须有互斥量的独占所有权。

效果:线程原子释放独占所有权并且获得升级所有权。此操作不阻塞。

抛出:一个派生自interprocess_exception的异常。

void unlock_and_lock_sharable()

前提条件:线程必须有互斥量的独占所有权。

效果:线程原子释放独占所有权并且获得共享所有权。此操作不阻塞。

抛出:一个派生自interprocess_exception的异常。

voidunlock_upgradable_and_lock_sharable()

前提条件:线程必须有互斥量的升级所有权。

效果:线程原子释放升级所有权并且获得共享所有权。此操作不阻塞。

抛出:一个派生自interprocess_exception的异常。

 

升级

voidunlock_upgradable_and_lock()

前提条件:线程必须有互斥量的升级所有权。

效果:线程原子释放升级所有权并且获得独占所有权。此操作将阻塞直到所有具有共享所有权的线程释放它为止。

抛出:一个派生自interprocess_exception的异常。

booltry_unlock_upgradable_and_lock()

前提条件:线程必须有互斥量的升级所有权。

效果:线程原子释放升级所有权并且尝试获得独占所有权。此操作将失败,如果有线程具有共享所有权。但是它会保持升级所有权。

返回:如果获得独占所有权,返回true。否则返回false。

抛出:一个派生自interprocess_exception的异常。

booltimed_unlock_upgradable_and_lock(const boost::posix_time::ptime &abs_time)

前提条件:线程必须有互斥量的升级所有权。

效果:线程原子释放升级所有权并且尝试获得独占所有权,如果需要,等待直到abs_time。此操作将失败,如果有线程具有共享所有权或超时。但是它会保持升级所有权。

返回:如果获得独占所有权,返回true。否则返回false。

抛出:一个派生自interprocess_exception的异常。

booltry_unlock_sharable_and_lock()

前提条件:线程必须有互斥量的共享所有权。

效果:线程原子释放共享所有权并且尝试获得独占所有权。此操作将失败,如果有线程具有共享或升级所有权。但是它会保持共享所有权。

返回:如果获得独占所有权,返回true。否则返回false。

抛出:一个派生自interprocess_exception的异常。

booltry_unlock_sharable_and_lock_upgradable()

前提条件:线程必须有互斥量的共享所有权。

效果:线程原子释放共享所有权并且尝试获得升级所有权。此操作将失败,如果有线程具有共享或升级所有权。但是它会保持共享所有权。

返回:如果获得升级所有权,返回true。否则返回false。

抛出:一个派生自interprocess_exception的异常。

 

Boost.Interprocess升级互斥量类型和头文件

Boost.Interprocess提供如下的升级互斥量类型:

#include 
  • interprocess_upgradable_mutex:一种非递归的、匿名的升级互斥量,它能被置于共享内存或内存映射文件中。
#include 
  • named_upgradable_mutex:一种非递归的,具名的升级互斥量。

 

共享锁和升级锁

共享锁和升级锁头文件

和普通互斥量一样,甚至在发生异常时,也能及时释放锁是很重要的。 Boost.Interprocess互斥量最好使用scoped_lock工具,并且这个类仅提供独占锁定。

因为我们有采用升级互斥量的共享锁定和升级锁定,因此我们有两种新工具:sharable_lock 和 upgradable_lock。这两个类都与scoped_lock类似,但是 sharable_lock在构造时需要共享锁,upgradable_lock在构造时需要升级锁。

这两个工具能与任何能够提供需要操作的同步对象一起使用。例如,一个用户自定义的无升级锁特征的互斥量类型能够使用sharable_lock ,如果同步对象提供lock_sharable() 和unlock_sharable()操作。

 

共享锁和升级锁头文件

#include 
#include 

共享锁在析构函数中调用unlock_sharable(),升级锁在析构函数中调用 unlock_upgradable(),因此当有异常发生时,升级互斥量也总能解锁。局部锁有许多构造函数来lock, try_lock, timed_lock 一个互斥量或解锁。

using namespace boost::interprocess;
 
//Let's create any mutex type:
MutexType mutex;
 
{
   //This will call lock_sharable()
   sharable_lock lock(mutex);
 
   //Some code
 
   //The mutex will be unlocked here
}
 
{
   //This won't lock the mutex()
   sharable_lock lock(mutex, defer_lock);
 
   //Lock it on demand. This will call lock_sharable()
   lock.lock();
 
   //Some code
 
   //The mutex will be unlocked here
}
 
{
   //This will call try_lock_sharable()
   sharable_lock lock(mutex, try_to_lock);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
   //If the mutex was locked it will be unlocked
}
 
{
   boost::posix_time::ptime abs_time = ...
 
   //This will call timed_lock_sharable()
   scoped_lock lock(mutex, abs_time);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
   //If the mutex was locked it will be unlocked
}
 
{
   //This will call lock_upgradable()
   upgradable_lock lock(mutex);
 
   //Some code
 
   //The mutex will be unlocked here
}
 
{
   //This won't lock the mutex()
   upgradable_lock lock(mutex, defer_lock);
 
   //Lock it on demand. This will call lock_upgradable()
   lock.lock();
 
   //Some code
 
   //The mutex will be unlocked here
}
 
{
   //This will call try_lock_upgradable()
   upgradable_lock lock(mutex, try_to_lock);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
   //If the mutex was locked it will be unlocked
}
 
{
   boost::posix_time::ptime abs_time = ...
 
   //This will call timed_lock_upgradable()
   scoped_lock lock(mutex, abs_time);
 
   //Check if the mutex has been successfully locked
   if(lock){
      //Some code
   }
   //If the mutex was locked it will be unlocked
}

upgradable_lock 和sharable_lock 提供更多的特性和操作,更多详情可查阅它们的参考手册。

 

通过移动语义转移锁

简单锁转移

锁转移概要

转移未锁定的锁

转移失败

进程间通信为不支持右值引用的编译器使用它自己的转移语义仿真代码。这只是一个临时解决方案直到Boost转移语义库被接受。

局部所以及类似的工具提供了简单资源管理的可能性,但是采用例如升级互斥量这样的高级互斥量类型,就有如下操作:一个获取的锁类型被释放,另一个锁类型被原子获取。这由升级锁操作,例如unlock_and_lock_sharable()完成。

使用锁转移操作,这些操作能够有效的进行。一个锁转移操作显式指出一个锁拥有的互斥量通过执行原子解锁/加锁操作被转移到另一个锁。

 

简单锁转移

考虑一个线程在开始的时候会修改一些数据,但之后,它在很长的时间里仅读取数据。代码可以获取独占锁,修改数据然后原子释放独占锁并且获取共享锁。通过这一系列操作,我们可以保证没有其他线程在转移中能修改数据,并且更多的读线程能获取共享锁,从而提高并发性。如果没有锁转移操作,代码将编写为如下:

using boost::interprocess;
interprocess_upgradable_mutex mutex;
 
//Acquire exclusive lock
mutex.lock();
 
//Modify data
 
//Atomically release exclusive lock and acquire sharable lock.
//More threads can acquire the sharable lock and read the data.
mutex.unlock_and_lock_sharable();
 
//Read data
 
//Explicit unlocking
mutex.unlock_sharable();

这很简单,但是如果出现异常,就很难知道当异常抛出的时候互斥量拥有什么类型的锁,以及我们应该调用什么函数去解锁它。

try{
   //Mutex operations
}
catch(...){
   //What should we call? "unlock()" or "unlock_sharable()"
   //Is the mutex locked?
}

我们可以使用锁转移来简化所有的这些管理:

using boost::interprocess;
interprocess_upgradable_mutex mutex;
 
//Acquire exclusive lock
scoped_lock s_lock(mutex);
 
//Modify data
 
//Atomically release exclusive lock and acquire sharable lock.
//More threads can acquire the sharable lock and read the data.
sharable_lock(move(s_lock));
 
//Read data
 
//The lock is automatically unlocked calling the appropriate unlock
//function even in the presence of exceptions.
//If the mutex was not locked, no function is called.

如我们所看到的,无论在任何时候抛出异常,互斥量都能调用适当的unlock()或unlock_sharable()方法自动解锁。

 

锁转移概要

转移到局部锁

转移到升级锁

转移到共享锁

有许多锁转移操作,我们可以根据出现在升级互斥量操作中的操作来分类:

  • 保证成功和非阻塞:任何从更严格的锁至次严格的锁。局部的->升级的、局部的->共享的、升级的->共享的。
  • 不保证成功:如果没有其他线程已经获取此升级或独占锁,则从共享的->独占的操作可能成功。此操作是一个尝试操作。
  • 如果无限等待,保证能成功:从升级的->局部的转换将成功,但需要等待所有共享锁被释放。由于这是一个阻塞操作,我们也可以选择不无限等待而仅仅尝试或仅等待超时即可。

 

转移到局部锁

转移到局部锁仅在从upgradable_lock且请求阻塞操作情况下保证成功,这源于此操作需要等待直到所有的共享锁被释放的事实。使用者可以使用”try”或”timed”转移来避免无限锁定,但成功性得不到保证。

从sharable_lock 的转换从来得不到保证,因此仅允许尝试操作:

//Conversions to scoped_lock
{
   upgradable_lock  u_lock(mut);
   //This calls unlock_upgradable_and_lock()
   scoped_lock      e_lock(move(u_lock));
}
{
   upgradable_lock  u_lock(mut);
   //This calls try_unlock_upgradable_and_lock()
   scoped_lock      e_lock(move(u_lock, try_to_lock));
}
{
   boost::posix_time::ptime t = test::delay(100);
   upgradable_lock  u_lock(mut);
   //This calls timed_unlock_upgradable_and_lock()
   scoped_lock      e_lock(move(u_lock));
}
{
   sharable_lock    s_lock(mut);
   //This calls try_unlock_sharable_and_lock()
   scoped_lock      e_lock(move(s_lock, try_to_lock));
}

 

转移到升级锁

仅当从 scoped_lock转移为upgradable_lock 是保证成功的,因为局部锁是比升级锁更严格的锁定。此操作非阻塞。

从sharable_lock 的转换不保证成功,因此仅允许尝试操作:

//Conversions to upgradable
{
   sharable_lock    s_lock(mut);
   //This calls try_unlock_sharable_and_lock_upgradable()
   upgradable_lock  u_lock(move(s_lock, try_to_lock));
}
{
   scoped_lock      e_lock(mut);
   //This calls unlock_and_lock_upgradable()
   upgradable_lock  u_lock(move(e_lock));
}

转移到共享锁

所有转移至sharable_lock都保证是成功的,因为升级锁和局部所都比共享锁严格。这些操作非阻塞:

//Conversions to sharable_lock
{
   upgradable_lock  u_lock(mut);
   //This calls unlock_upgradable_and_lock_sharable()
   sharable_lock    s_lock(move(u_lock));
}
{
   scoped_lock      e_lock(mut);
   //This calls unlock_and_lock_sharable()
   sharable_lock    s_lock(move(e_lock));
}

 

转移未锁定的锁

在之前的例子中,使用在转移操作中的互斥量预先被锁定了:

  Mutex mut;
 
   //This calls mut.lock()
   scoped_lock      e_lock(mut);
 
   //This calls unlock_and_lock_sharable()
   sharable_lock    s_lock(move(e_lock));
}

但是使用一个未锁定的源来执行转移是可能的,基于显式解锁、try/timed或defer_lock构造函数:

//These operations can leave the mutex unlocked!
 
{
   //Try might fail
   scoped_lock      e_lock(mut, try_to_lock);
   sharable_lock    s_lock(move(e_lock));
}
{
   //Timed operation might fail
   scoped_lock      e_lock(mut, time);
   sharable_lock    s_lock(move(e_lock));
}
{
   //Avoid mutex locking
   scoped_lock      e_lock(mut, defer_lock);
   sharable_lock    s_lock(move(e_lock));
}
{
   //Explicitly call unlock
   scoped_lock      e_lock(mut);
   e_lock.unlock();
   //Mutex was explicitly unlocked
   sharable_lock    s_lock(move(e_lock));
}
如果源互斥量未被锁定:
  • 目标锁不执行原子unlock_xxx_和_lock_xxx操作。
  • 目标锁未被锁定。
  • 源锁被释放并且互斥量的所有权被转移至目标。
{
   scoped_lock      e_lock(mut, defer_lock);
   sharable_lock    s_lock(move(e_lock));
 
   //Assertions
   assert(e_lock.mutex() == 0);
   assert(s_lock.mutex() != 0);
   assert(e_lock.owns()  == false);
}

转移失败

当执行锁转移时,操作可能失败:

  • 被执行的原子互斥量解锁加锁函数可能会抛出异常。
  • 被执行的原子函数可能是”try”或”timed”函数,它们可能会失败。

在第一种情况中,互斥量的所有权没有转移并且源锁的析构函数会解锁互斥量:

{
   scoped_lock      e_lock(mut, defer_lock);
 
   //This operations throws because
   //"unlock_and_lock_sharable()" throws!!!
   sharable_lock    s_lock(move(e_lock));
 
   //Some code ...
 
   //e_lock's destructor will call "unlock()"
}

在第二种情况中,如果一个内部的 "try"或"timed"操作失败(返回”false”),则互斥量的所有权没有转移,源锁未改变并且目标锁的状态与默认的构造相同:

{
   sharable_lock    s_lock(mut);
 
   //Internal "try_unlock_sharable_and_lock_upgradable()" returns false
   upgradable_lock  u_lock(move(s_lock, try_to_lock));
 
   assert(s_lock.mutex() == &mut);
   assert(s_lock.owns()  == true);
   assert(u_lock.mutex() == 0);
   assert(u_lock.owns()  == false);
 
   //u_lock's destructor does nothing
   //s_lock's destructor calls "unlock()"
}

文件锁

什么是文件锁

文件锁操作

文件锁定中的局部锁和共享锁

小心:同步限制

小心iostream写入

 

什么是文件锁?

文件锁是一种进程间通信机制,它使用一个嵌入至文件中的互斥量来保护文件读写的并发操作。这种嵌入式互斥量具有共享锁和独占锁的能力。采用文件锁,现有的文件可以做为一个互斥量,而不需要创建额外的同步对象来控制文件读写的并发性。

一般来说,我们有两种文件锁定能力:

  • 咨询锁定:操作系统内核维护一个已被锁定文件的列表。但是甚至一个进程已经获得了一个共享锁,也不阻止向这些文件写数据或当一个进程已经获取了独占锁,也不阻止从文件中读数据。任何进程可以忽略一个咨询锁。这意味着咨询锁是用于合作进程的,进程间彼此信任。这与保护共享内存片内数据的互斥量类似:任何连接至那块内存的进程可以覆盖数据,但是合作的进程使用互斥量来保护数据首先获取互斥锁。
  • 强制锁定:操作系统内核检查每次读写请求,从而校验操作能按照获取的锁来执行。读/写均被阻塞直到锁被释放。

Boost.Interprocess 基于可移植性的原因使用咨询锁定。这意味着所有并发访问文件的进程需要合作使用文件锁来同步访问。

在一些系统中,文件锁定甚至能被进一步完善成记录锁定,在此情况下使用者能在文件中指定一个字节范围,然后在其上使用锁定。这允许数个进程的写操作能够并发进行,如果它们需要的只是对文件不同部分的访问。Boost.Interprocess目前提供记录锁定,但将来可能会提供。要使用文件锁,仅需包含:

#include 

文件锁定是一个具有进程生命周期的类。这意味着如果一个拥有文件锁的进程结束或崩溃了,操作系统会自动解锁它。这个特性在某些情况下非常有用,例如我们想保证甚至当进程崩溃的情况下自动解锁从而避免在系统中留下锁定了的资源。文件锁使用文件名为参数进行构造:

#include 
 
int main()
{
   //This throws if the file does not exist or it can't
   //open it with read-write access!
   boost::interprocess::file_lock flock("my_file");
   return 0;
}

 

文件锁操作

文件锁具有通常的互斥量操作,同时具有共享锁的能力。这意味着我们可以有很多读线程拥有共享锁,同时很多写线程拥有独占锁等待直到读线程完成它们的任务。

然后,文件锁支持升级锁定或升级或降级(锁转移),因此它比升级锁受限多。下面是操作:

void lock()

效果:调用者线程尝试获取文件锁的独占所有权。如果其他线程具有互斥量的独占或共享所有权,它等待直到能够获取所有权为止。

抛出:interprocess_exception异常。

bool try_lock()

效果:调用者线程尝试获取文件锁的独占所有权而不等待。如果没有其他线程具有互斥量的独占或共享所有权,此操作成功。

返回:如果立刻获取到了独占所有权返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

bool timed_lock(constboost::posix_time::ptime &abs_time)

效果:调用者线程尝试获取文件锁的独占所有权,等待直到没有其他线程拥有此文件锁的独占或共享所有权,或达到abs_time

返回:如果获取到了独占所有权返回true。否则返回false。

抛出:interprocess_exception异常。

void unlock()

前提条件:线程必须有文件锁的独占所有权。

效果:调用者线程释放文件锁的独占所有权。

抛出:一个派生自interprocess_exception的异常。

void lock_sharable()

效果:调用者线程尝试获取文件锁的共享所有权。如果其他线程具有互斥量的独占所有权,它等待直到能够获取所有权为止。

抛出:interprocess_exception异常。

bool try_lock_sharable()

效果:调用者线程尝试获取文件锁的共享所有权而不等待。如果没有其他线程具有互斥量的独占所有权,此操作成功。

返回:如果立刻获取到了共享所有权返回true。如果需要等待,返回false。

抛出:interprocess_exception异常。

booltimed_lock_sharable(const boost::posix_time::ptime &abs_time)

效果:调用者线程尝试获取文件锁的共享所有权,等待直到没有其他线程拥有此文件锁的独占所有权,或达到abs_time

返回:如果获取到了共享所有权返回true。否则返回false。

抛出:interprocess_exception异常。

void unlock_sharable()

前提条件:线程必须有文件锁的共享所有权。

效果:调用者线程释放文件锁的共享所有权。

抛出:一个派生自interprocess_exception的异常。

更多的文件锁方法,参考file_lock reference

 

文件锁定中的局部锁和共享锁

当出现异常时,scoped_lock和sharable_lock 能使文件锁定更容易,就好像与互斥量一样:

#include 
#include 
//...
 
using namespace boost::interprocess;
//This process reads the file
//    ...
//Open the file lock
file_lock f_lock("my_file");
 
{
   //Construct a sharable lock with the filel lock.
   //This will call "f_lock.sharable_lock()".
   sharable_lock sh_lock(f_lock);
 
   //Now read the file...
 
   //The sharable lock is automatically released by
   //sh_lock's destructor
}
#include 
#include 
 
//...
 
using namespace boost::interprocess;
//This process writes the file
//    ...
//Open the file lock
file_lock f_lock("my_file");
 
{
   //Construct a sharable lock with the filel lock.
   //This will call "f_lock.lock()".
   scoped_lock e_lock(f_lock);
 
   //Now write the file...
 
   //The exclusive lock is automatically released by
   //e_lock's destructor
}

然而,锁转移只允许在同样类型的锁间进行,即:从一个共享锁至另一个共享锁或从一个局部锁至另一个局部锁。从一个局部锁至一个共享锁的转移是不允许的,因为 file_lock没有锁升级或降级函数,例如unlock_and_lock_sharable()。这将导致编译错误:

//Open the file lock
file_lock f_lock("my_file");
 
scoped_lock e_lock(f_lock);
 
//Compilation error, f_lock has no "unlock_and_lock_sharable()" member!
sharable_lock e_lock(move(f_lock));

 

小心:同步限制

如果你打算和使用具名互斥量一样使用文件锁,那要小心了,因为可移植的文件锁有同步限制,这主要是因为不同的实现(POSIX,Windows)提供了不同的保证。进程间文件锁有如下限制:

  • 如果文件锁同步来自同一进程的两个线程,则结果未定义。
  • 如果一个进程能使用两个指向同一文件的file_lock 对象,则结果未定义。

第一个限制主要来自POSIX,因为文件句柄是一个全进程(per-process)属性而不是全线程属性。这意味着如果一个线程使用了一个file_lock对象来锁一个文件,另外的线程将检测到文件被锁定了。然而,Windows文件锁机制提供了进程同步保证,因此一个线程尝试去锁已经被锁定的文件,线程将阻塞。

第二个限制主要来自在Windows下文件锁定同步状态与一个单独的文件描述器绑定的事实。这意味着如果两个file_lock对象被创建为指向同一个文件,则不保证同步。在POSIX下,当两个文件描述符被用于锁一个文件,如果一个描述符关闭了,所有调用者进程设定的文件锁都被清除了。

归纳起来,如果你打算在进程中使用可移植的文件锁,须遵守如下限制:

  • 对每个文件,每个进程使用单独的file_lock对象。
  • 使用同一个线程锁定和解锁文件。
  • 如果你正在使用std::fstream/原生文件句柄写文件,同时使用文件锁在此文件上时,在释放所有文件锁前,不要关闭此文件。

 

小心iostream写入

正如我们所看到的,文件锁在同步两个进程时非常有用,但是在解锁文件锁前,应确保数据已经被写入至此文件。小心iostream类会有一些缓冲的措施,因此如果你想确保其他进程能够“看到”你写入的数据,可以有如下选择:

使用原生的文件函数(Unix系统下的read()/write()和Windows系统下的ReadFile/WriteFile),来代替iostream。

写操作在解锁文件锁前刷新数据,如果你正在使用标准的C函数,可以使用fflush,或者使用C++ iostreams的flush成员函数。在Windows下,你甚至不能使用其他类来访问同一个文件。

//...
 
using namespace boost::interprocess;
//This process writes the file
//    ...
//Open the file lock
fstream file("my_file")
file_lock f_lock("my_lock_file");
 
{
   scoped_lock e_lock(f_lock);
 
   //Now write the file...
 
   //Flush data before unlocking the exclusive lock
   file.flush();
}

 

消息队列

什么是消息队列

使用消息队列

 

什么是消息队列

消息队列类似于一个消息的链表。线程可以放置消息至此队列,也能从队列中删除消息。每个消息可以有一个优先级以便高优先级消息在低优先级消息前被读取。每个消息都有一些属性:

  • 优先级。
  • 消息长度。
  • 数据(如果长度大于0)

线程可以使用3种方法往消息队列中发送或读取一条消息:

  • 阻塞:在发送时如果队列已满或接收时队列已空,线程阻塞直到有新消息空间或有新消息。
  • 尝试:在发送时如果队列已满或接收时队列已空,线程马上返回,同时报个错误。
  • 延时:在发送时如果队列已满或接收时队列已空,线程尝试操作直到成功(返回成功状态)或超时(返回一个错误)。

消息队列仅在进程间拷贝原始字节,并且不发送对象。这意味着如果我们想通过消息队列发送一个对象,对象必须首先被二进制序列化。例如,我们可以在进程间发送整数,但不能发送std::string。你应该使用 Boost.Serialization或使用高级Boost.Interprocess通信机制在进程间发送复杂数据。

Boost.Interprocess消息队列是一种具名进程间通信:消息队列具名创建,具名打开,就好像一个文件一样。当创建一个消息队列,用户必须指定最大消息字节大小和消息队列能存储的最大消息数目。这些参数将定义资源(例如,如果使用共享内存,则是执行消息队列的共享内存的大小)。

using boost::interprocess;
//Create a message_queue. If the queue
//exists throws an exception
message_queue mq
   (create_only         //only create
   ,"message_queue"     //name
   ,100                 //max message number
   ,100                 //max message size
   );
using boost::interprocess;
//Creates or opens a message_queue. If the queue
//does not exist creates it, otherwise opens it.
//Message number and size are ignored if the queue
//is opened
message_queue mq
   (open_or_create      //open or create
   ,"message_queue"     //name
   ,100                 //max message number
   ,100                 //max message size
   );
using boost::interprocess;
//Opens a message_queue. If the queue
//does not exist throws an exception.
message_queue mq
   (open_only           //only open
   ,"message_queue"     //name
   );

消息队列采用调用静态的删除函数显式删除。

using boost::interprocess;
message_queue::remove("message_queue");
如果消息队列仍旧被其他进程使用,此函数将失败。

 

使用消息队列

为了使用消息队列,你必须包含如下头文件:

#include 

在下面的例子中,第一个进程创建消息队列,然后写入一个整型数组。另一个进程读取数组然后校验它们的正确性。下面是第一个进程:

#include 
#include 
#include 
 
using namespace boost::interprocess;
 
int main ()
{
   try{
      //Erase previous message queue
      message_queue::remove("message_queue");
 
      //Create a message_queue.
      message_queue mq
         (create_only               //only create
         ,"message_queue"           //name
         ,100                       //max message number
         ,sizeof(int)               //max message size
         );
 
      //Send 100 numbers
      for(int i = 0; i < 100; ++i){
         mq.send(&i, sizeof(i), 0);
      }
   }
   catch(interprocess_exception &ex){
      std::cout << ex.what() << std::endl;
      return 1;
   }
 
   return 0;
}

下面是第二个进程:

#include 
#include 
#include 
 
using namespace boost::interprocess;
 
int main ()
{
   try{
      //Open a message queue.
      message_queue mq
         (open_only        //only create
         ,"message_queue"  //name
         );
 
      unsigned int priority;
      message_queue::size_type recvd_size;
 
      //Receive 100 numbers
      for(int i = 0; i < 100; ++i){
         int number;
         mq.receive(&number, sizeof(number), recvd_size, priority);
         if(number != i || recvd_size != sizeof(number))
            return 1;
      }
   }
   catch(interprocess_exception &ex){
      message_queue::remove("message_queue");
      std::cout << ex.what() << std::endl;
      return 1;
   }
   message_queue::remove("message_queue");
   return 0;
}

若要了解更多关于此类的信息和所有的操作,请参考 message_queue

你可能感兴趣的:(C/C++)