我有一个表存储像数据这样的键值,它会经常使用但很少更新。所以我想将必要的数据存储在内存中,只有在更新到来时才更新它。
以下是显示我当前解决方案的简单代码。
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 - 将静态包装到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;
}
问题
除了这两个解决方案之外,还有其他可能解决这类问题的方法吗?
答案 0 :(得分:1)
当这两个静态变量被破坏时,另一个线程会尝试 要调用这两种方法,它就会失败。
这里真正的问题是你仍然在main()的末尾运行线程。那不好;即使你解决了这个特殊的问题,在关机时你会继续得到其他(类似的)竞争条件,其中一些你将无法解决。
正确的解决方法是确保所有生成的线程都已退出并保证在您对其可能访问的资源进行任何清理之前(例如在main()返回之前)(在这种情况下)。特别是,您需要告诉每个线程退出(例如,通过设置线程定期检查的std::atomic<bool>
或类似,或关闭线程正在监视的套接字,或者通过任何其他跨线程通知机制,您可以得出),然后主线程调用线程对象上的join(),这样主线程就会阻塞join(),直到子线程退出。
完成此操作后,关机期间将不再有竞争条件,因为不会有任何线程不适当地尝试访问正在删除的资源。
答案 1 :(得分:0)
使用间接 - 所有编程问题的解决方案。
为您的数据结构创建一个接口类 - 在本例中为两个方法query
和update
- 其中所有方法都是纯虚拟。
声明static是指向此接口类型的指针。
创建两个实现子类:一个是真实的,另一个什么都不做(但必要时返回默认值)。
在应用开始时创建一个真实实例,将其粘贴在静态指针中。在app退出时,创建一个do-nothing实例,将其交换到静态指针,并删除静态指针中 的实例。 (或者,如果应用程序/进程实际上已经消失,请不要删除它。)
由于此映射正在更新,因此它显然已经具有全局锁(或读写锁)。交换指针操作也需要获取该锁,以确保在交换时没有人在数据结构中。但是锁需要从数据结构移动到指针。最简单的方法是在接口的第三个子类中保存一个指向数据结构的指针(前面的静态指针&#39;)并将所有操作转发给包含的实例在采取适当的锁定之后。
(这听起来很复杂,但事实并非如此,而且在我们不得不将DLL加载到操作系统网络堆栈的情况下我自己完成了卸载直到操作系统重新启动,但是在升级应用程序时需要升级DLL的实现,其时间与需要重新启动操作系统无关。我提供了整个转发可以加载到操作系统中的DLL, it 加载/卸载/重新加载完成工作的实际 DLL,将所有操作转发给它,并跟踪不再使用旧的 DLL(返回所有操作)并且可以释放它。)
替代方案,除了真正的偏执之外是不必要的:do-nothing实例也可以声明为静态,然后你只需将一个指针放到app exit的静态指针到接口。它不需要清理(删除)。
你知道,如果这是一个应用程序生命周期的事情,并且流程正在被破坏,为什么不只是不根本不清理这个静态地图?