同步机制概述 互斥量 条件变量 信号量 升级互斥量 通过移动语义转移锁 文件锁 消息队列 |
具名和匿名同步机制 同步机制类型 |
如前所述,如果对内存的访问不能有效的同步,则通过内存映射文件或共享内存对象在进程间共享内存的能力就不是非常有用了。与需要在进程间共享堆栈和全局变量的进程间同步机制遇到的问题一样,访问这些资源一般需要使用互斥量或条件变量进行同步。Boost.Threads在同一进程的线程间实现了这些同步机制。Boost.Interprocess则在不同的进程间实现了类似的机制。
Boost.Interprocess提供了两种同步对象:
以上两种方式各有优缺点:
具名和匿名实用工具在接口上的不同主要反映在构造函数上。一般而言,匿名实用工具仅有一个构造函数,而具名实用工具有多个构造函数,它们的第一个参数是一个需要创建或打开底层资源的特殊类型:
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提供了如下互斥量类型:
#include
#include
#include
#include
当进程已经完成了读写数据后及时解锁互斥量,这是非常重要的。这一点在遇到异常时,是比较困难的。因此,互斥量一般是带局部锁使用,局部锁能保证互斥量一直能被解锁,哪怕有异常发生。为使用一个局部锁,仅需包含:
#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提供如下的条件类型:
#include
#include
具名条件与匿名条件类似,但它们结合具名互斥量使用。有时,我们不希望存储带同步数据的同步对象:
想象一下,一个进程写一份跟踪数据至简单共享内存缓冲区,另一个进程一个接一个打印出来。第一个进程写跟踪数据,然后等待直到另一个进程打印出这份数据。为达到此目的,我们可以使用两个条件变量:第一个用于阻塞发送者直到第二个进程打印出此消息,第二个用于阻塞接收者直到缓冲区中有数据供打印。
共享内存跟踪数据缓冲区(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信号量类型和头文件 匿名信号量示例 |
信号量是一种基于内部计数的进程间同步机制,它提供了两种基本操作:
如果信号量计数值被初始化为1,则等待操作与互斥锁等价,委派操作与互斥解锁等价。这种类型的信号量被称为二进制信号量。
尽管信号量可以像互斥量一样使用,它有一个独特的特性:与互斥量不同,委派操作不需要由执行等待操作的那个相同的线程/进程来执行。
Boost.Interprocess提供如下类型的信号量:
#include
#include
我们将在共享内存中实现一个整型数组,此数组将被用来从一个进程向另一个进程传递数据。第一个进程将向数组内写一些整数,并且如果数组满了,进程将阻塞。
第二个进程将拷贝传输过来的数据至其自身的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提供如下的升级互斥量类型:
#include
#include
共享锁和升级锁头文件
和普通互斥量一样,甚至在发生异常时,也能及时释放锁是很重要的。 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));
}
如果源互斥量未被锁定:
{
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);
}
当执行锁转移时,操作可能失败:
在第一种情况中,互斥量的所有权没有转移并且源锁的析构函数会解锁互斥量:
{
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)提供了不同的保证。进程间文件锁有如下限制:
第一个限制主要来自POSIX,因为文件句柄是一个全进程(per-process)属性而不是全线程属性。这意味着如果一个线程使用了一个file_lock对象来锁一个文件,另外的线程将检测到文件被锁定了。然而,Windows文件锁机制提供了进程同步保证,因此一个线程尝试去锁已经被锁定的文件,线程将阻塞。
第二个限制主要来自在Windows下文件锁定同步状态与一个单独的文件描述器绑定的事实。这意味着如果两个file_lock对象被创建为指向同一个文件,则不保证同步。在POSIX下,当两个文件描述符被用于锁一个文件,如果一个描述符关闭了,所有调用者进程设定的文件锁都被清除了。
归纳起来,如果你打算在进程中使用可移植的文件锁,须遵守如下限制:
正如我们所看到的,文件锁在同步两个进程时非常有用,但是在解锁文件锁前,应确保数据已经被写入至此文件。小心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();
}
什么是消息队列 使用消息队列 |
消息队列类似于一个消息的链表。线程可以放置消息至此队列,也能从队列中删除消息。每个消息可以有一个优先级以便高优先级消息在低优先级消息前被读取。每个消息都有一些属性:
线程可以使用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
。