使用静态映射在C++中缓存数据的推荐方法是什么

What's the recommended way to use static map to cache data in C++

本文关键字:数据 是什么 方法 缓存 静态 映射 C++      更新时间:2023-10-16

我有一个表,存储一个类似数据的键值,它将被频繁使用,但很少更新。所以我想把必要的数据存储在内存中,只在更新到来时更新。

以下是显示我当前解决方案的简单代码。

kv.h

class kv
{
public:
string query(string key);
void update(string key, string value);
};

kv.cpp

#include "kv.h"
#include <map>
#include <mutex>
#include <thread>
static map<string, string> s_cacheMap;
static mutex mtx;
string kv::query(string key)
{
unique_lock<mutex> lock(mtx);
if (s_cacheMap.empty())
{
// load from db
}
auto it = s_cacheMap.find(key);
if (it != s_cacheMap.end())
{
return (*it).second;
}
return "";
};
void kv::update(string key, string value)
{
unique_lock<mutex> lock(mtx);
s_cacheMap.clear();
// write key value into db
};

此解决方案的问题

这些代码将成为C++编写的iOS平台库的一部分。该应用程序可能随时被系统或用户杀死。我可以在应用程序退出时收到通知,但在用户终止应用程序之前,我只有很短的时间来清理。我不能保证这些线程在应用程序终止时仍在运行,得到正确的结果,但我想确保它不会崩溃。

在应用程序生命周期结束时,这两个静态变量将被销毁。当这两个静态变量被破坏时,另一个线程试图调用这两个方法,它将失败。

可能的解决方案

1-将静态数据包装成类似的方法

map<string, string>& getCacheMap()
{
static map<string, string> *s_cacheMap = new map<string, string>;
return *s_cacheMap;
}

2-将kv类设为单一

static kv& getInstance()
{
static kv* s_kv = new kv();
return *s_kv;
}

问题

除了这两种解决方案之外,这类问题还有其他可能的解决方案吗?

当这两个静态变量被破坏时,另一个线程尝试要调用这两个方法,它将失败。

这里真正的问题是在main()的末尾仍然有线程在运行。这不好;即使你解决了这个特殊的问题,你也会在关机时继续受到其他(类似的)比赛条件的影响,其中一些你将无法解决。

正确的修复方法是确保所有派生线程都已退出,并保证在清理它们可能访问的资源之前(例如,在本例中,在main()返回之前)都已离开。特别是,您需要告诉每个线程退出(例如,通过设置std::atomic<bool>或类似的线程定期检查,或关闭线程正在监视的套接字,或通过您可以想到的任何其他跨线程通知机制),然后让主线程在线程对象上调用join(),这样主线程就会阻塞join(()内部,直到子线程退出为止。

一旦你做到了,在关闭期间就不会有更多的竞争条件,因为不会有线程不适当地试图访问正在删除的资源。

使用间接寻址-所有编程问题的解决方案。

为数据结构创建一个接口类——在本例中是两个方法queryupdate——其中所有方法都是纯虚拟的。

将static声明为指向此接口类型的指针。

创建两个实现子类:一个是真正的子类,另一个什么都不做(但在必要时返回默认值)。

在应用程序启动时创建一个实际实例,将其粘贴在静态指针中。在应用程序退出时,创建一个无所事事的实例,将其交换到静态指针中,并删除静态指针中的真实实例。(或者,如果应用程序/进程实际上正在消失,则不要删除它。)

由于这个映射正在更新,它显然已经有了全局锁(或读写锁)。交换指针操作也需要获取该锁,以确保在交换数据结构时没有人在数据结构中。但锁需要从数据结构移动到指针。最简单的方法是拥有接口的第三个子类,该子类持有指向数据结构的指针(前一个"静态指针"),并在获得正确的锁后将所有操作转发到包含的实例。

(这听起来很复杂,但事实并非如此,我自己也这样做过,在这种情况下,我们必须将DLL加载到操作系统网络堆栈中,在操作系统重新启动之前,它将无法卸载,但在应用程序升级时,DLL的实现需要升级,时间与重新启动操作系统无关可以加载到操作系统中的整个转发DLL,加载/卸载/重新加载完成工作的实际DLL,将所有操作转发给它,并跟踪旧的DLL何时不再使用(返回所有操作)并可以释放。)

可选的,除了真正的偏执狂之外是不必要的:无所事事的实例也可以被声明为静态的,然后你只需在应用程序出口处将指向它的指针放入指向接口的静态指针中。它不需要清理(删除)。

你知道,如果这是一个应用程序生命周期的事情,而且这个过程无论如何都会被破坏,为什么不清理这个静态映射呢?