2power10
2power10

Reputation: 1279

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

I have a table store a key value like data, which will be frequently used but rarely update. So I would like to store necessary data in the memory, and only update it when the update coming.

Here is the simple code show my current solution.

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
};

Problem of this solution

Those code will be part of the library in the iOS platform wrote by C++. The app might be killed by system or user at anytime. I could get notification when app exit, but I only have a very short time to clean up before user terminate the app. I couldn't guarantee those threads still running when application is terminating get correct result, but I'd like to make sure it doesn't crash.

At the end of the application lifecycle, those two static variable will be destroyed. When those two static variable have been destroyed, another thread try to call those two method, it will fail.

Possible solutions

1 - Wrap the static into a method like that

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

2 - Make kv class as singleton

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

Problem

Beside those two solutions, is there any other possible solution for that kind of problem?

Upvotes: 1

Views: 750

Answers (2)

davidbak
davidbak

Reputation: 5999

Use indirection - the solution to all programming problems.

Create an interface class to your data structure - in this case two methods, query and update - where all methods are pure virtual.

Declare the static to be a pointer to this interface type.

Create two implementation subclasses: one is the real one, the other does nothing (but return default values where necessary).

At app start time create a real instance, stick it in the static pointer. At app exit time, create a do-nothing instance, swap it into the static pointer, and delete the real instance that was in the static pointer. (Or don't delete it if the app/process is actually going away.)

Since this map is being updated it obviously already has a global lock (or read-write lock). The swap-pointer operation needs to take that lock too, to make sure nobody is in the data structure while you swap it. But the lock needs to moved to the pointer from the data structure. Easiest way to do that is to have a third subclass of the interface which holds a pointer to the data structure (the previous 'static pointer') and forwards all operations to the contained instance after taking the proper lock.

(This sounds complex, but it isn't really, and I've done it myself in a situation where we had to load a DLL into an OS network stack, where it would stay without being able to be unloaded until the OS was rebooted, but where the implementation of the DLL needed to be upgraded when the app was upgraded, the time of which happened independently of needing to reboot the OS. I provided an entire forwarding DLL which could be loaded into the OS, and it loaded/unloaded/reloaded the actual DLL that did the work, forwarding all operations to it, and tracking when the older DLL was no longer used (all operations returned) and could be freed.)

Alternative, unnecessary except for the truely paranoid: The do-nothing instance could be declared static too, then you just put a pointer to it into the static pointer-to-interface at app exit. It doesn't need to be cleaned up (deleted).

You know, if this is an application lifecycle thing, and the process is getting destroyed anyway, why not just not clean up this static map at all?

Upvotes: 0

Jeremy Friesner
Jeremy Friesner

Reputation: 73041

When those two static variable have been destroyed, another thread try to call those two method, it will fail.

Your real problem here is that you still have threads running at the end of main(). That's no good; even if you work around this particular problem, you will continue to get bit by other (similar) race conditions on shutdown, some of which you won't be able to work around.

The proper fix is to make sure that all spawned threads have exited and are guaranteed to be gone before you do any cleanup of resources they might access (e.g. before main() returns, in this case). In particular, you need to tell each thread to exit (e.g. by setting a std::atomic<bool> or similar that the thread checks periodically, or closing a socket that the thread is monitoring, or by any other cross-thread notification mechanism you can come up with), and then have the main thread call join() on the thread object so that the main thread will block inside join() until the child thread has exited.

Once you've done that, there will be no more race conditions during shutdown, because there will be no threads left to inappropriately try to access the resources that are being deleted.

Upvotes: 1

Related Questions