c++ - Herb Sutter 的 10 衬里清洁

标签 c++ caching mutex

我一直在为我的代码添加数据缓存,并记得“Herb Sutter 最喜欢的 10-liner”。

shared_ptr<widget> get_widget(int id) {
    static map<int, weak_ptr<widget>> cache;
    static mutex m;

    lock_guard<mutex> hold(m);
    auto sp = cache[id].lock();
    if (!sp) cache[id] = sp = load_widget(id);
    return sp;
}

我看到的一个批评是,根据上下文,没有从缓存中删除任何内容可能是一个问题。

我很惊讶在任何地方都找不到自行清理的解决方案。你怎么看这个?

std::shared_ptr<widget> get_widget(int id) {
    static std::map<int, std::weak_ptr<widget>> cache;
    static std::mutex m;

    std::lock_guard<std::mutex> hold(m);
    auto& weakCached = cache[id]; // Keep a reference so we don't hae to call this non-nothrow function below.
    auto sp = weakCached.lock();
    if (!sp) {
        std::unique_ptr<widget> it = load_widget(id);
        // We have to be careful about the deleter not being
        // called while in the mutex lock 'cause that'd be a 
        // double lock => deadlock.
        // For that reason, we already have a 
        auto deleter = [&,id,d=it.get_deleter()](widget* w) {
            std::lock_guard<std::mutex> hold(m);
            d(w);
            cache.erase(id);
        };
        sp = std::shared_ptr<widget>(it.get(), deleter);
        it.release(); // In the case that the above line throws, we won't hit this line and so won't leak.
        weakCached = sp;
    }
    return sp;
}

正如评论所说,让我停顿的部分是所有权转移到 deleter功能。特别是,调用它会锁定互斥体,因此在互斥体被锁定时无法调用它。我认为这正确地保证了它不会在关键部分内被调用,特别是因为如果 sp = std::shared_ptr<widget>(it.get(), deleter);抛出,那么它仍然属于 unique_ptr下面一行让它过去。

奖励积分:如果 load_widget(int id) 会怎么样?返回 std::shared_ptr<widget>它可能已经有一个非平凡的删除器。有没有办法在它周围注入(inject)这个包装器,或者这是不可能的?

最佳答案

我相信有一种更简单的方法可以保证删除器不会在hold 时运行。 持有锁:声明sp之前 hold ,所以当我们超出范围时,互斥量将在删除程序运行之前被释放:

std::shared_ptr<widget> sp;
std::lock_guard<std::mutex> hold(m);
auto& weakCached = cache[id];
sp = weakCached.lock();

重用 weakCached 是有意义的甚至不必关心异常安全。


What if load_widget(int id) returns a std::shared_ptr<widget>

这是一个(疯狂的?)想法:

std::shared_ptr<widget> it = load_widget(id);
widget* ptr = it.get();
auto deleter = [&,id,original=std::move(it)](widget*) {
    std::lock_guard<std::mutex> hold(m);
    cache.erase(id);
};
weakCached = sp = std::shared_ptr<widget>(ptr, std::move(deleter));

我们保留了 load_widget 的结果拷贝在删除器中。这会导致我们的共享指针保持原始共享指针处于事件状态。当我们的引用计数用完时,我们的自定义删除器不会直接对小部件执行任何操作。删除器被简单地销毁,因此捕获的共享指针也被销毁,如果它是唯一的所有者,那么原始的自定义删除器将完成它的工作。

这种方法有一个警告:如果原始共享指针的拷贝以某种方式在其他地方可用,那么它的生命周期可能会延长到我们共享指针的生命周期之外,因此缓存可能会被过早清除。是否是这种情况,以及这是否是一个问题取决于相关的小部件 API。

这种方法也可以用于您独特的指针情况。

关于c++ - Herb Sutter 的 10 衬里清洁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49782011/

相关文章:

c++ - XCode 升级到 4.4.1,现在我在 STL <vector> 中编译时遇到错误

caching - 为什么L1和L2 Cache浪费空间来保存相同的数据?

jQuery 对象 : to cache or not to cache?

c# - 创建 Mutex 时出现 "Could not find a part of the path"错误

c++ - 析构函数 C++ 中的异常

c++ - 运行简单的单线程 C++ 程序时,是否可以消除 Linux 机器的抖动?

c++ - 特殊结构 : grouping of (pointers to function and groupings of pointers to function)

java - Redis Replication和Cluster区别

c++ - 递归互斥锁背后的想法

c - pthread_mutex_lock 仅适用于 sleep