美文网首页
一个简单的线程安全的LRU_Cache实现

一个简单的线程安全的LRU_Cache实现

作者: lwj_ow | 来源:发表于2019-11-21 21:45 被阅读0次

前言

本文首发于我的公众号:码农手札,主要介绍linux下c++开发的知识包括网络编程的知识同时也会介绍一些有趣的算法题,欢迎大家关注,利用碎片时间学习一些编程知识,冰冻三尺非一日之寒,让我们一起加油!
实习过程中遇到一个场景,在多线程下用到一个缓存当前分析的结果的map,当时只是用了一个map来解决这个问题,但是当时就觉得这个办法不是很好,因为分析结果基本上是很快就会被更新掉的,但是如果一个结果被用到,说明其很可能会被继续用到,分析了一下场景,我们就能很明了的想到,应该是用LRU来解决这个问题,LRU是一种被广泛使用的缓存算法,在操作系统以及一些开源的库中都能见到其身影,这里我们也对LRU也做出一个简单的拓展,实现一个模板LRUC_Cache类

实现代码

#include <list>
#include <mutex>
#include <unordered_map>

template <typename T> using LISTT = std::list<T>;

template <typename T, typename Y>
using PAIRTL = std::pair<Y, typename LISTT<T>::iterator>;

template <typename T, typename Y>
using UMAPTP = std::unordered_map<T, PAIRTL<T, Y>>;

template <typename T, typename Y> class LRU_Cache {
public:
  LRU_Cache(const size_t &capacity) : capacity_(capacity) {}
  Y Get(const T &key) {
    std::lock_guard<std::mutex> lck(cache_mutex_);
    auto it = cache_.find(key);
    if (it == cache_.end()) {
      return Y();
    }
    touch(it);
    return it->second.first;
  }
  void Put(const T &key, const Y &value) {
    std::lock_guard<std::mutex> lck(cache_mutex_);
    auto it = cache_.find(key);
    if (it != cache_.end()) {
      touch(it);
    } else {
      if (cache_.size() == capacity_) {
        cache_.erase(elements_.back());
        elements_.pop_back();
      }
      elements_.push_front(key);
    }
    cache_[key] = {value, elements_.begin()};
  }

private:
  void touch(typename UMAPTP<T, Y>::iterator it) {
    ///注意这里不能加锁,否则就死锁了,因为这个函数不会被单独调用,总是在有锁的情况下
    ///被调用,所以函数本身不需要再加锁
    T key = it->first;
    elements_.erase(it->second.second);
    elements_.push_front(key);
    it->second.second = elements_.begin();
  }
  size_t capacity_;
  ///由于element_和cache_都是被同时访问,因此一个锁就够了
  std::mutex cache_mutex_;
  LISTT<T> elements_;
  UMAPTP<T, Y> cache_;
};

简单解释

不考虑多线程的情况下(其实考虑也没啥,只有一个锁哈哈),我们使用来一个list来储存所有的key,使用unorderedmap来保存key,然后value是一个pair,pair的首元素是value,次元素是指向key在list中的位置的迭代器,这里list的作用其实只是标示元素的顺序,因为lru算法要求每当一个元素被access,它就应该排到队列的最前端,当lru的缓存已经满了的时候,就应该删除队列中最后面的元素,因为这个元素是least_recently_used(lru),unorderedmap的作用也就很明显了,一方面储存value,一方面储存key在list中对应的迭代器,方便进行删除操作。

至于实现的话,这里有个小技巧,就是抽离出了一个touch函数,这个函数的作用是根据一个确定已经在缓存中的元素的key,访问一次,其目的就是改变元素在队列中的位置,同时unorderedmap中的迭代器也需要更新,本质上就是把元素挪到队列头。

其他的应该都比较直接,反而模板这一块由于自己不太熟,写那个using声明弄了蛮久,说起来还怪不好意思

总结

这个实现虽然简单,但是基本效果还是能达到的,我自己也做了简单的测试,可能不是很全面,但是基本还是能用的,毕竟代码也相对比较简单(笑),

相关文章

  • 一个简单的线程安全的LRU_Cache实现

    前言 本文首发于我的公众号:码农手札,主要介绍linux下c++开发的知识包括网络编程的知识同时也会介绍一些有趣的...

  • lru_cache的简单实现

    主要代码出处,轮子就自己不造了,就做一些简单的修改。感谢源代码作者。

  • 单例设计模式

    饿汉式,线程安全 但效率比较低 饱汉式,非线程安全 饱汉式,线程安全简单实现 线程安全 并且效率高 单例模式最优...

  • ConcurrentHashMap源码分析

    前言 JDK中的Hashtable是一个线程安全的K-V形式的容器,它实现线程安全的原理十分简单,就是在所有涉及对...

  • 简单实现自己的Lock

    简述 我们知道使用ReentrantLock可以实现同步,保证线程安全,下面我们来简单实现自己的Lock 实现 我...

  • Ehcache缓存回收策略

    Ehcache缓存 简介 Ehcache是一个用JAVA实现的,使用简单,高速,实现线程安全的缓存管理类库,Ehc...

  • 二.线程安全原理简介

    一.实现线程安全的原理 锁的线程安全实现原理:AQS+CASAQS:AbstractQueuedSynchroni...

  • JAVA面试问题集(更新中)

    什么是线程安全线程安全代码 多线程的实现方式继承Thread类实现Runnable接口实现Callback接口 什...

  • SpringBoot中引入Ehcache3.x

    1.Ehcache介绍 Ehcache是一个用Java实现的简单、高速、线程安全的缓存管理类库。具体快速、简单、低...

  • 单例模式学习

    Ⅰ 懒汉式-线程不安全 Ⅱ 饿汉式-线程安全 Ⅳ 双重校验锁-线程安全 Ⅴ 静态内部类实现 Ⅵ 枚举实现 学习参考...

网友评论

      本文标题:一个简单的线程安全的LRU_Cache实现

      本文链接:https://www.haomeiwen.com/subject/ymsxwctx.html