延迟初始化缓存...如何使其成为线程安全的?

时间:2011-11-11 17:06:21

标签: c++ windows multithreading caching double-checked-locking

这就是我所拥有的:

  • Windows服务
    • C#
    • 多线程
    • 该服务使用Read-Write-Lock(一次多次读取,写入其他读/写线程)
  • 一个简单的,自编的DB
    • C ++
    • 足够小以适应记忆
    • 足够大,不想在启动时加载它(例如10GB)
    • 阅读表现非常重要
    • 写作不太重要
    • 树状结构
    • 树节点中保存的信息存储在文件
    • 为了提高性能,文件仅在第一次使用和缓存时加载
    • 延迟初始化以加快数据库启动速度

由于数据库将经常访问这些节点信息(每秒数千次)并且由于我不经常写,所以我想使用某种双重检查锁定图案。

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

以下是一个例子:

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

2 个答案:

答案 0 :(得分:4)

关于双重检查锁定:

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

编辑:没有原子

#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。 根据{{​​3}}指针分配在x86和x64系统中将是原子的。 volatile确保不会缓存resource的值。

答案 1 :(得分:1)

您是否在询问如何阅读数据库或读取节点线程安全?

如果你正在尝试后者并且你不经常写作,那么为什么不制作你的节点immutable,句号?如果您需要编写内容,请从现有节点复制数据,修改它并创建另一个节点,然后将其放入数据库中。