1

这就是我所拥有的:

  • Windows 服务
    • C#
    • 多线程
    • 该服务使用读写锁(一次多次读取,写入阻塞其他读/写线程)
  • 一个简单的自写数据库
    • C++
    • 小到可以放入内存
    • 足够大,不想在启动时加载它(例如 10GB)
    • 读取性能非常重要
    • 写作不那么重要
    • 树状结构
    • 树节点中保存的信息存储在文件中
    • 为了获得更快的性能,文件仅在第一次使用和缓存时加载
    • 延迟初始化以加快数据库启动

由于数据库会经常访问这些节点信息(每秒几千次),而且我不经常写,我想使用某种双重检查锁定模式。

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

这是一个例子:

  • 一棵有 100 万个节点的树
  • 每个节点存储一个键值对列表(存储在一个文件中用于持久性,文件大小大小:10kB)
  • 当第一次访问一个节点时,列表被加载并存储在一个地图中(比如std::map)
  • 下次访问这个节点的时候,我就不用再加载文件了,直接从map中获取就可以了。
  • 唯一的问题:两个线程第一次同时访问该节点并想要写入缓存映射。这不太可能发生,但也不是不可能。这就是我需要线程安全的地方,这不应该花费太多时间,因为我通常不需要它(尤其是当整个数据库都在内存中时)。
4

2 回答 2

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. 根据这个链接指针分配将在 x86 和 x64 系统中是原子的。并volatile确保resource不会缓存 的值。

于 2011-11-14T10:35:00.973 回答
1

您是在问如何使读取 DB 或读取 Nodes 线程安全吗?

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

于 2011-11-11T17:13:27.910 回答