如何实现线程安全的LRU缓存驱逐?
How to implement thread-safe LRU cache eviction?
我已经实现了一个 LRU 缓存 (code),我想将其用于具有 N 个元素和完全 N^2(所有对)匹配的多线程匹配问题。理想情况下,我会直接从缓存中获取对每个元素的引用以节省内存。
匹配两个元素(我们称它们为 A 和 B)所花费的时间可能会有很大差异,我担心如果一对元素需要很长时间才能匹配另一个线程(工作速度非常快)并处理许多对)将导致 A 或 B 从缓存中逐出,从而使引用无效。
一个简单的解决方案是不使用引用,但我想知道是否有更好的方法来确保如果元素是 "currently used" 或对它们有引用,则不会被逐出?
为避免逐出正在使用的对象,可以使用 std::shared_ptr
的引用计数功能。考虑以下实现:
#include <iostream>
#include <string>
#include <memory>
#include <map>
#include <algorithm>
template <typename K, typename V> class cache
{
public:
cache() {}
static const constexpr int max_cache_size = 2;
std::shared_ptr<V> getValue(const K& k)
{
auto iter = cached_values.find(k);
if (iter == cached_values.end()) {
if (cached_values.size() == max_cache_size) {
auto evictIter =
std::find_if(cached_values.begin(), cached_values.end(),
[](const auto& kv) { return kv.second.second.unique(); });
if (evictIter == cached_values.end()) {
std::cout << "Nothing to evict\n";
return nullptr;
}
cached_values.erase(evictIter);
}
static V next;
iter = cached_values.insert(std::make_pair(k, std::make_pair(++next, nullptr))).first;
iter->second.second = std::shared_ptr<V>(&iter->second.first, [](const auto&) {});
}
return iter->second.second;
}
std::map<K, std::pair<V, std::shared_ptr<V>>> cached_values;
};
int main()
{
cache<int, int> c;
std::cout << *c.getValue(10) << "\n";
std::cout << *c.getValue(20) << "\n";
std::cout << *c.getValue(30) << "\n";
auto useOne = c.getValue(10);
auto useTwo = c.getValue(20);
std::cout << *c.getValue(20) << "\n"; // We can use stuff that is still in cache
std::cout << c.getValue(30); // Cache is full, also note no dereferencing
}
基本上,只要缓存外的任何人使用 returned 值,std::shared_ptr::unique
就会 return false,从而使缓存条目不可逐出。
我已经实现了一个 LRU 缓存 (code),我想将其用于具有 N 个元素和完全 N^2(所有对)匹配的多线程匹配问题。理想情况下,我会直接从缓存中获取对每个元素的引用以节省内存。
匹配两个元素(我们称它们为 A 和 B)所花费的时间可能会有很大差异,我担心如果一对元素需要很长时间才能匹配另一个线程(工作速度非常快)并处理许多对)将导致 A 或 B 从缓存中逐出,从而使引用无效。
一个简单的解决方案是不使用引用,但我想知道是否有更好的方法来确保如果元素是 "currently used" 或对它们有引用,则不会被逐出?
为避免逐出正在使用的对象,可以使用 std::shared_ptr
的引用计数功能。考虑以下实现:
#include <iostream>
#include <string>
#include <memory>
#include <map>
#include <algorithm>
template <typename K, typename V> class cache
{
public:
cache() {}
static const constexpr int max_cache_size = 2;
std::shared_ptr<V> getValue(const K& k)
{
auto iter = cached_values.find(k);
if (iter == cached_values.end()) {
if (cached_values.size() == max_cache_size) {
auto evictIter =
std::find_if(cached_values.begin(), cached_values.end(),
[](const auto& kv) { return kv.second.second.unique(); });
if (evictIter == cached_values.end()) {
std::cout << "Nothing to evict\n";
return nullptr;
}
cached_values.erase(evictIter);
}
static V next;
iter = cached_values.insert(std::make_pair(k, std::make_pair(++next, nullptr))).first;
iter->second.second = std::shared_ptr<V>(&iter->second.first, [](const auto&) {});
}
return iter->second.second;
}
std::map<K, std::pair<V, std::shared_ptr<V>>> cached_values;
};
int main()
{
cache<int, int> c;
std::cout << *c.getValue(10) << "\n";
std::cout << *c.getValue(20) << "\n";
std::cout << *c.getValue(30) << "\n";
auto useOne = c.getValue(10);
auto useTwo = c.getValue(20);
std::cout << *c.getValue(20) << "\n"; // We can use stuff that is still in cache
std::cout << c.getValue(30); // Cache is full, also note no dereferencing
}
基本上,只要缓存外的任何人使用 returned 值,std::shared_ptr::unique
就会 return false,从而使缓存条目不可逐出。