延迟初始化caching…如何使其线程安全?

这就是我所拥有的:

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

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

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

这是一个例子:

  • 一百万个节点的树
  • 每个节点存储一个键值对的列表(存储在一个文件中用于保存,文件大小为10kB)
  • 当第一次访问一个节点时,这个列表被加载并存储在一个映射中(像std :: map那样)
  • 下次访问这个节点时,我不必再次加载文件,我只是从地图上获取它。
  • 唯一的问题是:两个线程第一次同时访问节点,并想要写入caching映射。 这是不太可能发生的,但这不是不可能的。 这是我需要线程安全的地方,不需要太多的时间,因为我通常不需要它(特别是一旦整个DB在内存中)。

Solutions Collecting From Web of "延迟初始化caching…如何使其线程安全?"

关于双重检查锁定:

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的价值不会被缓存。

你问如何读取数据库或读取节点线程安全吗?

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