什么是使用静态地图来缓存C++数据的推荐方式
我有一个表存储一个像数据这样的关键值,这个关键值将会被频繁使用但很少更新。所以我想将必要的数据存储在内存中,并且只在更新到来时才更新它。什么是使用静态地图来缓存C++数据的推荐方式
下面是简单的代码显示我目前的解决方案。
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
};
此溶液
的问题这些代码将在iOS平台库的一部分由C++编写。该应用程序可能随时被系统或用户杀死。我可以在应用程序退出时收到通知,但在用户终止应用程序之前,我只有很短的时间才能清理。我无法保证这些线程在应用程序终止时仍然运行得到正确的结果,但我想确保它不会崩溃。
在应用程序生命周期结束时,这两个静态变量将被销毁。当这两个静态变量被销毁时,另一个线程尝试调用这两个方法,它将失败。
可能的解决方案
1 - 裹静态成方法等that
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()内部阻塞,直到子线程退出。
一旦你做到了这一点,就会关闭期间没有更多的竞争条件,因为不会有螺纹留下不适当尝试访问被删除的资源。
你的建议很好,但前提是我完全了解应用程序的生命周期。我在iOS平台上提供了一个库,这个应用程序可能在任何时候都被用户或系统杀死,并且只剩下有限的时间等待所有线程退出。 – 2power10
你可以添加一个MyLibrary_Shutdown()调用到你的库的API并要求主机程序在退出之前调用该函数吗?这将处理控制退出案件;在不受控制的退出/强制退出的情况下,你可以做的事情不多。 (或者如果问题是你认为你的线程一旦通知就会花费太长时间才能退出,那么解决方案就是确保你的线程快速退出;例如,通过使用事件驱动的通知机制而不是定期轮询) –
使用间接 - 解决所有编程的问题。
创建的接口类的数据结构 - 在这种情况下两种方法,query
和update
- 在所有的方法都是纯虚。
声明静态为指向此接口类型的指针。
创建两个实现子类:一个是真实的,其他什么也不做(但返回默认值在必要时)。
在应用程序启动时创建一个真实的实例,把它贴在静态指针。在应用程序退出时,创建一个无所事事的实例,将其交换为静态指针,并删除静态指针中的实例为。 (还是不删除,如果应用程序/过程实际上是要离开。)
由于本地图正在更新它显然已经有一个全局锁(或读写锁)。交换指针操作也需要使用该锁,以确保在交换数据结构时没有人处于数据结构中。但是锁需要移动到数据结构的指针。最简单的方法是有一个接口的第三个第三个子类,它包含一个指向数据结构的指针(前一个'静态指针'),并在采取适当的锁定之后将所有操作转发给包含的实例。
(这听起来很复杂,但事实并非如此,我自己也是在我们不得不将DLL加载到操作系统网络堆栈的情况下自己完成的,在这种情况下,它将停留在不能卸载的地方直到操作系统重新启动,但是当升级应用程序时需要升级DLL的实现,这个时间发生的时间不需要重新启动操作系统,我提供了一个可以加载到操作系统的整个转发DLL ,并且它加载/卸载/重新加载实际 DLL做了工作,将所有操作转发给它,以及跟踪何时更旧的 DLL不再被使用(所有操作返回)并且可以被释放。 )
替代,除了忠实地偏执不必要的:在什么都不做的情况下,可以声明为static过,那么你只要把它的指针到静态指针到界面的应用程序退出。它不需要清理(删除)。
你知道,如果这是一个应用程序生命周期的事情,并且该过程被破坏掉了,无论如何,为什么不不收拾这个静态地图呢?
没有一些指针交换技术会受到竞争条件的影响 - 例如,当主线程获得CPU时,子线程可能已经读取了“实例”指针并准备使用它,将指针交换出去,并释放共享资源。然后几毫秒后,子线程再次运行,尝试使用(现在无效的)实实例指针并使进程崩溃。 –
@JeremyFriesner - 真的,我会更新答案。 – davidbak
在所有线程完成之前不要退出程序? – davidbak
我同意来自'davidbak'的上述评论,你应该确保所有的线程在破坏上述静态变量被销毁之前完成。这更多的是通过堆栈调用操作符的顺序问题,而不是有效使用静态映射或缓存数据。 –
@davidbak我在C++编写的iOS平台上提供了一个库。所以这个应用程序可能随时被系统或用户杀死。我可以在应用程序退出时收到通知,但在用户终止应用程序之前,我只有很短的时间才能清理。我无法保证这些线程在应用程序终止时仍然运行得到正确的结果,但我想确保它不会崩溃。 – 2power10