0

I currently have a program that has a cache like mechanism. I have a thread listening for updates from another server to this cache. This thread will update the cache when it receives an update. Here is some pseudo code:

void cache::update_cache()
{
    cache_ = new std::map<std::string, value>();
    while(true)
    {
        if(recv().compare("update") == 0)
        {
            std::map<std::string, value> *new_info = new std::map<std::string, value>();
            std::map<std::string, value> *tmp;
            //Get new info, store in new_info
            tmp = cache_;
            cache_ = new_cache;
            delete tmp;              
        }
    }
}

std::map<std::string, value> *cache::get_cache()
{
    return cache_;
}

cache_ is being read from many different threads concurrently. I believe how I have it here I will run into undefined behavior if one of my threads call get_cache(), then my cache updates, then the thread tries to access the stored cache.

I am looking for a way to avoid this problem. I know I could use a mutex, but I would rather not block reads from happening as they have to be as low latency as possible, but if need be, I can go that route.

I was wondering if this would be a good use case for a unique_ptr. Is my understanding correct in that if a thread calls get_cache, and that returns a unique_ptr instead of a standard pointer, once all threads that have the old version of cache are finished with it(i.e leave scope), the object will be deleted.

Is using a unique_ptr the best option for this case, or is there another option that I am not thinking of?

Any input will be greatly appreciated.

Edit:

I believe I made a mistake in my OP. I meant to use and pass a shared_ptr not a unique_ptr for cache_. And when all threads are finished with cache_ the shared_ptr should delete itself.

A little about my program: My program is a webserver that will be using this information to decide what information to return. It is fairly high throughput(thousands of req/sec) Each request queries the cache once, so telling my other threads when to update is no problem. I can tolerate slightly out of date information, and would prefer that over blocking all of my threads from executing if possible. The information in the cache is fairly large, and I would like to limit any copies on value because of this.

update_cache is only run once. It is run in a thread that just listens for an update command and runs the code.

Eumcoz
  • 2,388
  • 1
  • 21
  • 44
  • 1
    Use a [readers-writer lock](http://en.wikipedia.org/wiki/Readers–writer_lock). [Boost provides one](http://stackoverflow.com/questions/989795/example-for-boost-shared-mutex-multiple-reads-one-write/6450576#6450576). As well, the same advice that you may be able to use RCU from [here](http://stackoverflow.com/a/22492335/1074413) applies. – Matthew G. Mar 25 '14 at 22:02
  • That is defiantly doable, however, if possible I would like to avoid blocking. Is there some sort of undefined behavior that would result from using unique pointers? Is there a reason why a read/write lock is a better solution? Also a note, I can tolerate slightly out of date information. – Eumcoz Mar 25 '14 at 22:49
  • 1
    I suppose my qualm here actually is related to the design, not so much the implementation. First, a `unique_ptr` doesn't appear to work, as only one thread legally can hold the `unique_ptr` which defeats multiple threads having access to the cache. Even if you can work around that though, how do the client threads know they need to update? I feel like your cache should be exposing an interface, not just dumping the actual cache pointer on clients and letting them run wild. If you can have clients query the cache, then an RCU like approach might be perfect -- zero overhead reads! – Matthew G. Mar 25 '14 at 23:43
  • Opps, sorry, my mistake. I believe I mean a `shared_ptr` not a `unique_ptr`. I made a little update to my original post to describe my program a little better. My threads querying the cache won't need to know when the cache is updated, they are processing web requests, and will use whatever information is provided. I could provide an interface, but the dataset is large, I would have to pass by reference to be efficient. So I would like some sort of smart pointer to take care of whatever data I am using(whether it be a smart_ptr on value and pass that, or a smart_ptr on the map. – Eumcoz Mar 26 '14 at 13:56

2 Answers2

2

I feel there are multiple issues:

1) Do not leak memory: for that never use "delete" in your code and stick with unique_ptr (or shared_ptr in specific cases)

2) Protect accesses to shared data, for that either using locking (mutex) or lock-free mecanism (std::atomic)

class Cache {
    using Map = std::map<std::string, value>();
    std::unique_ptr<Map> m_cache;
    std::mutex m_cacheLock;
public:

    void update_cache()
    {
        while(true)
        {
            if(recv().compare("update") == 0)
            {
                std::unique_ptr<Map> new_info { new Map };
                //Get new info, store in new_info
                {
                   std::lock_guard<std::mutex> lock{m_cacheLock};
                   using std::swap;
                   swap(m_cache, new_cache);
                }
            }
        }
    }

Note: I don't like update_cache() being part of a public interface for the cache as it contains an infinite loop. I would probably externalize the loop with the recv and have a:

    void update_cache(std::unique_ptr<Map> new_info)
    {
        { // This inner brace is not useless, we don't need to keep the lock during deletion
           std::lock_guard<std::mutex> lock{m_cacheLock};
           using std::swap;
           swap(m_cache, new_cache);
        }
    }

Now for the reading to the cache, use proper encapsulation and don't leave the pointer to the member map escape:

    value get(const std::string &key)
    {
        // lock, fetch, and return. 
        // Depending on value type, you might want to allocate memory
        // before locking
    }

Using this signature you have to throw an exception if the value is not present in the cache, another option is to return something like a boost::optional.

Overall you can keep a low latency (everything is relative, I don't know your use case) if you take care of doing costly operations (memory allocation for instance) outside of the locking section.

Joky
  • 1,608
  • 11
  • 15
  • This defiantly looks feasible. Please see my edit for a little more clarification. Based on that, do you see any reason to externalize the loop? Your lock and swap looks like it will work. The reason I didn't add an interface to get a value was for a couple of reasons. 1) I do not want to return value by value, it is a large object, and I would like to return by reference. 2) If I returned a reference to a value, and then swapped pointers, I believe the reference will cause undefined behavior. I believe I made a mistake in my initial post, I should use and return a shared_ptr for cache_. – Eumcoz Mar 26 '14 at 13:48
  • 1
    Yes if value is a large object and you have to avoid copy, then returning a shared_ptr is probably better. You can still have your cache defined as std::map>, and return std::shared_ptr from get. I still wouldn't have the thread method with this name as part of the interface for the cache, I would split the class in two parts. Something that I wonder is why your cache updates are not incremental: adding/deleting only one entry at a time instead of invalidating the full cache for a brand new one. Especially for the use case you mention. – Joky Mar 26 '14 at 15:09
  • That is the nature of my data. My data has situations where a single element can be invalidated from a single key, or a set of elements can be invalidated from multiple keys. Key organizes the internal data into pre-queried buckets as well, so I do not always know which values are in which buckets. Value is also a group of data that must be iterated over, the overhead to keep all my my keys up to date would outweigh the simplicity of just re-querying the data. I think I may use an interface return a shared_ptr to value, it does sound much neater as threads only use one key at a time. – Eumcoz Mar 26 '14 at 16:09
1

shared_ptr is very reasonable for this purpose, C++11 has a family of functions for handling shared_ptr atomically. If the data is immutable after creation, you won't even need any additional synchronization:

class cache {
public:
    using map_t = std::map<std::string, value>;
    void update_cache();
    std::shared_ptr<const map_t> get_cache() const;
private:
    std::shared_ptr<const map_t> cache_;
};

void cache::update_cache()
{
    while(true)
    {
        if(recv() == "update")
        {
            auto new_info = std::make_shared<map_t>();
            // Get new info, store in new_info
            // Make immutable & publish
            std::atomic_store(&cache_,
                              std::shared_ptr<const map_t>{std::move(new_info)});
        }
    }
}

auto cache::get_cache() const -> std::shared_ptr<const map_t> {
    return std::atomic_load(&cache_);
}
Casey
  • 41,449
  • 7
  • 95
  • 125
  • Thank you, I believe this is what I was looking for. Unfortunately for me, my compiler does not support std::atomic_store/atomic_load for shared pointers(running GCC 4.7.2), and I am unable to upgrade my compiler(running on CentOS). I translated it all over to boost equivalents. It seems to be working so far. – Eumcoz Mar 27 '14 at 14:44