延迟初始化缓存..我如何使它线程安全

Lazy initialized caching... how do I make it thread-safe?

本文关键字:何使它 线程 安全 初始化 缓存 延迟      更新时间:2023-10-16

这就是我所拥有的:

  • 一个Windows服务
      c#
    • 多线程
    • 服务使用Read-Write-Lock(一次读多个,写阻塞其他读写线程)
  • 一个简单的,自己编写的数据库
      c++
    • 足够小,可以装入内存
    • 足够大,不想在启动时加载它(例如10GB)
    • 读性能非常重要
    • 文字不那么重要
    • 树结构
    • 树节点中的信息存储在文件
    • 为了更快的性能,文件只在第一次使用时加载并缓存
    • 延迟初始化以加快数据库启动

由于DB将非常频繁地访问这些节点信息(每秒数千次),并且由于我不经常写入,因此我希望使用某种双重检查锁定模式。

我知道这里有很多关于双重检查锁定模式的问题,但是似乎有很多不同的意见,所以我不知道什么对我的情况是最好的。你会怎么处理我的装置?

下面是一个例子:

  • 拥有100万个节点的树
  • 每个节点存储一个键值对列表(存储在一个文件中用于持久化,文件大小大小:10kB)
  • 当第一次访问一个节点时,列表被加载并存储在一个映射中(类似std::map)
  • 下一次访问这个节点时,我不需要再次加载文件,我只需要从地图中获取它。
  • 唯一的问题:两个线程第一次同时访问节点,并且想要写入缓存映射。这种情况不太可能发生,但也不是不可能。这就是我需要线程安全的地方,它不应该花费太多时间,因为我通常不需要它(特别是当整个DB都在内存中时)。

关于双重检查锁定:

class Foo
{
  Resource * resource;
  Foo() : resource(nullptr) { }
public:
  Resource & GetResource()
  {
    if(resource == nullptr)
    {
      scoped_lock lock(mutex); 
      if(resource == nullptr)
        resource = new Resource();
    }
    return *resource;
  }
}

当您检查资源的地址是否为空时,它不是线程安全的。因为在初始化指向它的resource对象之前,有可能将资源指针赋给非空值。

但是使用c++ 11的"原子"特性,您可能有一个双重检查的锁定机制。

class Foo
{
  Resource * resource;
  std::atomic<bool> isResourceNull;
public:
  Foo() : resource(nullptr), isResourceNull(true) { }
  Resource & GetResource()
  {
    if(isResourceNull.load())
    {
      scoped_lock lock(mutex); 
      if(isResourceNull.load())
      {
        resource = new Resoruce();
        isResourceNull.store(false);
      }
    }
    return *resource;
  }
}

EDIT: Without atomics

#include <winnt.h>
class Foo
{
  volatile Resource * resource;
  Foo() : resource(nullptr) { }
public:
  Resource & GetResource()
  {
    if(resource == nullptr)
    {
      scoped_lock lock(mutex); 
      if(resource == nullptr)
      {
        Resource * dummy = new Resource();
        MemoryBarrier(); // To keep the code order
        resource = dummy;  // pointer assignment
      }
    }
    return  *const_cast<Resource*>(resource);
  }
}

MemoryBarrier()确保首先创建dummy,然后将其分配给resource。根据这个链接指针的分配在x86和x64系统中是原子的。volatile保证resource的值不被缓存

您是否在问如何使读取DB或读取节点线程安全?

如果你正在尝试后者,并且你不经常写,那么为什么不让你的节点不可变呢?如果您需要写一些东西,那么从现有节点复制数据,修改它并创建另一个节点,然后您可以将其放入数据库。