虽然互斥量是保护共享数据最常用的手段,但却不是唯一的。有些共享数据,只需要在初始化时保护,比如共享数据是只读的,在初始化之后就不用再保护了。
有些资源,在需要时才初始化:
std::shared_ptr<some_resource> resource_ptr;
void foo()
{
if(!resource_ptr)
{
rescource_ptr.reset(new some_resource);
}
returnce_ptr->do_something();
}
上面函数中,在资源初始化时应该保护才对,保证多线程环境下,资源初始化时不被打断。
std::shared_ptr<some_resource> resource_ptr;
void foo()
{
std::unique_lock<std::mutex> lk(resource_mutex);
if(!resource_ptr)
{
rescource_ptr.reset(new some_resource);
}
lk.unlock();
returnce_ptr->do_something();
}
在if前面保护共享资源。在资源已经初始化的情况下,如果一个线程正在执行std::unique_lock后面,lk.unlock前面代码,这时再有线程执行的话会等待。只有在资源初始化时才需要保护,
所以锁可以加到if后面
std::shared_ptr<some_resource> resource_ptr;
void foo()
{
if(!resource_ptr)
{
std::lock_guard<std::mutex> lk(resource_mutex);
if(!resource_ptr)
{
rescource_ptr.reset(new some_resource);
}
}
resournce_ptr->do_something();
}
上面代码其实还有个bug。构造资源时使用了锁,但是在使用资源时没有使用锁。如果资源构造时间比较久,在构造了一半时,resource_ptr已经不是NULL,但是还没有构造完。这时有另外一个线程来调用,就会出错。
C++标准委员会已经为这种情况提供了解决的方法:使用std::once_flag和std::call_once。
std::shared_ptr<some_resource> rescource_ptr;
std::once_flag rescource_flag;
void init_resource()
{
rescource_ptr.reset(new some_resource);
}
void foo()
{
std::call_once(resource_flag,init_resource);//保证只执行一次
resource_ptr->do_something();
}
有些共享数据很少更新,例如DNS文件,很可能在几个月甚至更长一段时间都不会更新,如果每次在使用DNS文件时都加锁,那么效率就降低很多。我们知道,Linux中有种锁叫“读写锁”(reader-wiriteer mutex),但是C++11不支持。这时可以使用boost库中的shared_mutex。
#include<map>
#include<string>
#include<mutex>
#include<boost/thread/shared_mutex.hpp>
class dns_entry;
class dns_cash
{
std::map<std::string dns_entry> entries;
mutable boost::shared_mutex entry_mutex;//互斥量
public:
dns_entry find_entry(std::string const& domain) const
{
boost::shared_lock<boost::shared_mutex> lk(entry_mutex);//相当于读锁
std::map<std::string,dns_entry>::const_iterator const it=
entriex.find(domain);
return (it==entries.end())?dns_entry:it->second;
}
void update_or_add_entry(std::string const& domain,
dns_entry const& dns_details)
{
//上写锁,非共享
std::lock_guard<boost::shared_mutex> lk(entry_mutex);
entries[domain].dns_details;
}
};
如果对一个互斥量两次上锁,会死锁。但是有些时候的确需要这样做,那么就可以使用递归锁std::recursive_mutex。它工作原理和mutex相同,但是可以给它多次上锁,多次上锁后要多次解锁。
在大多数情况下,不需要使用递归锁。如果你要使用,很可能你需要更改你的设计。在陈硕的《Linux多线程服务器编程》中有讲到不建议使用递归锁的原因:递归锁和非递归锁性能差别不大,递归锁只是多了一个计数器。使用非递归锁可以让我们在编码时思考代码对锁的期求,及早发现问题。
原文地址:http://blog.csdn.net/kangroger/article/details/40152965