使用异常实现cache

Posted on April 9, 2017

异常用来做错误处理的时候,程序到处都是 try cache ,代码十分的丑陋,我是不怎么喜欢的,我喜欢 asio 那种用 error_code 汇报错误 —— 不传 ec 的时候就抛异常,传就不抛,改为写入错误到 ec。

但是,异常用来做流程控制,又特别的好用。流程控制,无非顺序、选择、分支和循环。在 c++里,又比 C 多了一个异常。在嵌套很深的地方,跳出逻辑,除了异常,就没有其他更好的办法了。

在编写软件的时候,时常需要对一些数据做 cache。在使用的时候,要先检查 cache,存在则使用 cache ,不存在则按照老办法办,然后存入 cache。

每次使用前都进行判断, 污染了快速路径的代码,对有简洁洁癖的程序员来说,内心是十分的纠结的。

这个时候, 你就需要 异常。将 cache hit 作为正常的流程进行编写, 假定全部的数据都是在 cache 里的。这万一发生了 cache miss , 则抛出异常,并在异常处理重新载入数据。然后重启处理。

说到重启处理, 在 Windows 的 SEH 里,存在 EXECEPT_CONTINUE_EXECUTION 这个异常处理的结果, windows 看到异常处理函数返回这个,就会回到发生异常的地方重新执行。然而这毕竟是一个 Windows 系统特有的 SEH , 而且依赖底层CPU提供的机制。 编写C++是断然不能使用这套机制的。

思考的最终结果,就是下面这样的结构


for (int =0; i < retry_times ; i++)
{ 
    try  
    {
            auto  v = cache_map_sometype.get_cache(key);
            // process with v ....
            ........
    }  
    catch(cache_miss&)
    {   
           // load v from other resources, database, filesystem, network, etc.
           ......
            cache_map_sometype.add_cache(key, v);
            contine; // NOTE about this.
    }
    break;  
}

在正常处理流程里, 执行到最后会有个 break 退出 for 循环。所以 for 循环在 cache hit 的状态下只执行一次。在 cache miss 的时候, catch block 里最后有一行 continue 。 于是就重启处理过程了。

下面给出 cache_map 的代码。


#pragma once

#include <tuple>
#include <map>

#include <boost/thread.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/date_time/posix_time/ptime.hpp>

struct cache_miss {};

template<typename KeyType, typename ValueType, int cache_aging_time = 30>
class cache_map
	: protected std::map<KeyType, std::tuple<ValueType, boost::posix_time::ptime>>
{
	typedef std::map<KeyType, std::tuple<ValueType, boost::posix_time::ptime>> base_type;

public:
	ValueType get_cache(const KeyType& key) throw(cache_miss)
	{
		boost::shared_lock<boost::shared_mutex> l(m_mutex);

		typename base_type::iterator it = base_type::find(key);

		if (it == base_type::end())
		{
			throw cache_miss();
		}

		std::tuple<ValueType, boost::posix_time::ptime> & value_pack = it->second;

		auto should_be_after = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(cache_aging_time);

		if (std::get<1>(value_pack) > should_be_after)
			return std::get<0>(value_pack);
		throw cache_miss();
	}

	void remove_cache(const KeyType& k)
	{
		boost::unique_lock<boost::shared_mutex> l(m_mutex);

		base_type::erase(k);
	}

	ValueType get_cache(const KeyType& key) const throw(cache_miss)
	{
		boost::shared_lock<boost::shared_mutex> l(m_mutex);

		typename base_type::const_iterator it = base_type::find(key);

		if (it == base_type::end())
		{
			throw cache_miss();
		}

		const std::tuple<ValueType, boost::posix_time::ptime> & value_pack = it->second;

		auto should_be_after = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(cache_aging_time);

		if (std::get<1>(value_pack) > should_be_after)
			return std::get<0>(value_pack);
		throw cache_miss();
	}

	void add_to_cache(const KeyType& k , const ValueType& v)
	{
		boost::unique_lock<boost::shared_mutex> l(m_mutex);

		base_type::erase(k);

		base_type::insert(std::make_pair(k, std::make_tuple(v, boost::posix_time::second_clock::universal_time())));
	}

	void tick()
	{
		boost::upgrade_lock<boost::shared_mutex> readlock(m_mutex);
		std::shared_ptr<boost::upgrade_to_unique_lock<boost::shared_mutex>> writelock;
		auto should_be_after = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(30);

		for (auto it = base_type::begin(); it != base_type::end(); )
		{
			const std::tuple<ValueType, boost::posix_time::ptime> & value_pack = it->second;

			if (std::get<1>(value_pack) < should_be_after)
			{
				if (!writelock)
					writelock.reset(new boost::upgrade_to_unique_lock<boost::shared_mutex>(readlock));
				base_type::erase(it++);
			}
			else
				it++;
		}
	}

private:
	mutable boost::shared_mutex m_mutex;
};

template<typename KeyType, typename ValueType>
class cache_map <KeyType, ValueType, 0>
	: protected std::map<KeyType, ValueType>
{
	typedef std::map<KeyType, ValueType> base_type;
public:

	ValueType& get_cache(const KeyType& key) throw(cache_miss)
	{
		boost::shared_lock<boost::shared_mutex> l(m_mutex);

		auto it = base_type::find(key);

		if (it == base_type::end())
		{
			throw cache_miss();
		}

		return it->second;
	}

	void add_to_cache(const KeyType& k , const ValueType& v)
	{
		boost::unique_lock<boost::shared_mutex> l(m_mutex);

		base_type::erase(k);

		base_type::insert(std::make_pair(k, v));
	}

	void tick()
	{
	}

private:
	mutable boost::shared_mutex m_mutex;
};
Comments