【问题标题】:How to implement cache manager using std::shared_ptr?如何使用 std::shared_ptr 实现缓存管理器?
【发布时间】:2014-08-14 07:28:36
【问题描述】:
#include <mutex>
#include <assert.h>
#include <iostream>
#include <unordered_map>
#include <memory>
#include <string>
#include <stdio.h>

// 
// Requirements:
//  1: the bitmap could be used by multiple thread safely.(std::shared_ptr could?)
//  2: cache the bitmap and do not always increase memeory
//@NotThreadSfe
struct Bitmap {
    public:
        Bitmap(const std::string& filePath) { 
            filePath_ = filePath;
            printf("foo %x ctor %s\n", this, filePath_.c_str());
        }
        ~Bitmap() {
            printf("foo %x dtor %s\n", this, filePath_.c_str());
        }
        std::string filePath_;
};

//@ThreadSafe
struct BitmapCache {
    public:
        static std::shared_ptr<Bitmap> loadBitmap(const std::string& filePath) {
            mutex_.lock();

            //whether in the cache
            auto iter = cache_.find(filePath);
            if (iter != cache_.end()) {
                if ((*iter).second) {
                    return (*iter).second;
                } else {
                    std::shared_ptr<Bitmap> newPtr(new Bitmap(filePath));
                    (*iter).second = newPtr;
                    return newPtr;
                }
            }

            //try remove unused elements if possible
            if (cache_.size() >= kSlotThreshold) {
                std::unordered_map<std::string,std::shared_ptr<Bitmap>>::iterator delIter = cache_.end();
                for (auto iter = cache_.begin(); iter != cache_.end(); ++iter) {
                    auto& item = *iter;
                    if (item.second && item.second.use_count() == 1) {
                        delIter = iter;
                        break;
                    }
                }
                if (cache_.end() != delIter) {
                    (*delIter).second.reset();
                    cache_.erase(delIter);
                }
            }

            //create new and insert to the cache
            std::shared_ptr<Bitmap> newPtr(new Bitmap(filePath));
            cache_.insert({filePath, newPtr});
            mutex_.unlock();
            return newPtr;
        }
    private:
        static const int kSlotThreshold = 20;
        static std::mutex mutex_;
        static std::unordered_map<std::string,std::shared_ptr<Bitmap>> cache_;
};

/* static */
std::unordered_map<std::string,std::shared_ptr<Bitmap>> BitmapCache::cache_;

/* static */
std::mutex BitmapCache::mutex_;

int main()
{
    //test for remove useless element
    char buff[200] = {0};
    std::vector<std::shared_ptr<Bitmap>> bmpVec(20);
    for (int i = 0; i < 20; ++i) {
        sprintf_s(buff, 200, "c:\\haha%d.bmp", i);
        bmpVec[i] = BitmapCache::loadBitmap(buff);
    }
    bmpVec[3].reset();
    std::shared_ptr<Bitmap> newBmp = BitmapCache::loadBitmap("c:\\new.bmp");

    //test for multiple threading...(to be implemenetd)
    return 0;
}

我是 C++ 内存管理的新手。您能否给我一个提示:我的方式是否正确,或者我应该采用不同的设计策略或不同的内存管理器策略(例如weak_ptr等)?

【问题讨论】:

    标签: c++ caching c++11 memory-management


    【解决方案1】:

    这让我想起了 Herb Sutter 在 GoingNative 2013 上的 "Favorite C++ 10-Liner" 演讲,稍作改编:

    std::shared_ptr<Bitmap> get_bitmap(const std::string & path){
        static std::map<std::string, std::weak_ptr<Bitmap>> cache;
        static std::mutex m;
    
        std::lock_guard<std::mutex> hold(m);
        auto sp = cache[path].lock();
        if(!sp) cache[path] = sp = std::make_shared<Bitmap>(path);
        return sp;
    }
    

    评论:

    1. 始终使用std::lock_guard 而不是在互斥体上调用lock()unlock()。后者更容易出错并且不是异常安全的。请注意,在您的代码中,前两个 return 语句从未解锁互斥锁。
    2. 这里的想法是在缓存中使用weak_ptr 跟踪分配的位图对象,因此缓存从不自己保持位图处于活动状态。这样就无需手动清理不再使用的对象 - 当最后一个引用它们的 shared_ptr 被破坏时,它们会被自动删除。
    3. 如果以前从未见过path,或者如果相应的Bitmap 已被删除,cache[path].lock() 将返回一个空的shared_ptr;下面的if 语句然后加载Bitmapmake_shared,将生成的shared_ptr 移动分配到sp,并设置cache[path] 以跟踪新创建的位图。
    4. 如果path对应的Bitmap还活着,那么cache[path].lock()会创建一个新的shared_ptr引用它,然后返回。

    【讨论】:

    • 太糟糕了,这个缓存没有做太多的缓存。如果我每帧都需要一个位图来绘制背景,它每次都会加载并丢弃它。它更像是一个“加载器/去重器”而不是“缓存/管理器”。
    • @DanielKO:它肯定是一种缓存,只是不是自己保留元素的缓存。只要有人保持引用活动,它就会缓存对象。这可能是件坏事,但也可能是件好事。您也可以很容易地让这个缓存做一些不同的事情,方法是保持一个额外的引用(只要需要一个位图,或者可能在一个具有 5-10 帧生命周期的对象内,每帧递减一个计数器,或类似的——这可能允许在内存紧张的情况下更好地操作,因为东西不会无限期地保留)。
    • 从 c++17 开始,更喜欢 scoped_lock 到 lock_guard
    • 此方法永远不会从地图中删除已发布的条目。我们还需要一个清理例程来迭代地图并检查weak_ptrs 是否过期。
    猜你喜欢
    • 1970-01-01
    • 2012-03-01
    • 2013-01-13
    • 2014-01-11
    • 1970-01-01
    • 2017-08-10
    • 2016-01-19
    • 2014-12-15
    • 2019-08-24
    相关资源
    最近更新 更多