首页 > 代码库 > 数据共享之其他可供选择的手段

数据共享之其他可供选择的手段

虽然互斥量是保护共享数据最常用的手段,但却不是唯一的。有些共享数据,只需要在初始化时保护,比如共享数据是只读的,在初始化之后就不用再保护了。

在共享数据初始化时保护

有些资源,在需要时才初始化:

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多线程服务器编程》中有讲到不建议使用递归锁的原因:递归锁和非递归锁性能差别不大,递归锁只是多了一个计数器。使用非递归锁可以让我们在编码时思考代码对锁的期求,及早发现问题。

 

 



 


 

 

数据共享之其他可供选择的手段