首页 > 代码库 > 数据共享之其他可供选择的手段
数据共享之其他可供选择的手段
虽然互斥量是保护共享数据最常用的手段,但却不是唯一的。有些共享数据,只需要在初始化时保护,比如共享数据是只读的,在初始化之后就不用再保护了。
在共享数据初始化时保护
有些资源,在需要时才初始化:
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多线程服务器编程》中有讲到不建议使用递归锁的原因:递归锁和非递归锁性能差别不大,递归锁只是多了一个计数器。使用非递归锁可以让我们在编码时思考代码对锁的期求,及早发现问题。
数据共享之其他可供选择的手段