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

发布于 2024-12-15 07:27:35 字数 787 浏览 4 评论 0原文

这就是我所拥有的:

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

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

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

举个例子:

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

that's what I have:

  • a Windows Service
    • C#
    • multithreaded
    • the service uses a Read-Write-Lock (multiple reads at one time, writing blocks other reading/writing threads)
  • a simple, self-written DB
    • C++
    • small enough to fit into memory
    • big enough not wanting to load it on startup (e.g. 10GB)
    • read-performance is very important
    • writing is less important
    • tree structure
    • informations held in tree nodes are stored in files
    • for faster performance, the files are loaded only the first time they are used and cached
    • lazy initialization for faster DB startup

As the DB will access those node informations very often (in the magnitude of several thousand times a second) and as I don't write very often, I'd like to use some kind of double checked locking pattern.

I know there is many questions about the double checked locking pattern here, but there seems to be so many different opinions, so I don't know what's the best for my case. What would you do with my setup?

Here's an example:

  • a tree with 1 million nodes
  • every node stores a list of key-value-pairs (stored in a file for persistence, file size magnitude: 10kB)
  • when accessing a node for the first time, the list is loaded and stored in a map (sth. like std::map)
  • the next time this node is accessed, I don't have to load the file again, I just get it from the map.
  • only problem: two threads are simultaneously accessing the node for the first time and want to
    write to the cache-map. This is very unlikely to happen, but it is not impossible. That's where I need thread-safety, which should not take too much time, as I usually don't need it (especially, once the whole DB is in memory).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

绝不放开 2024-12-22 07:27:35

关于双重检查锁定:

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

它不是线程安全的,因为您检查资源的地址是否为空。因为在初始化指向它的资源对象之前,资源指针有可能被分配给一个非空值。

但是使用 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 系统中是原子的。并且易失性确保resource的值不会被缓存。

About double checked locking:

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

It is not thread-safe as you check whether the address of resource is null. Because there is a chance that resource pointer is assigned to a non-null value right before the initializing the Resource object pointed to it.

But with the "atomics" feature of C++11 you may have a doubly checked locking mechanism.

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() ensures that dummy will be first created then assigned to resource.
According to this link pointer assignments will be atomic in x86 and x64 systems. And volatile ensures that the value of resource will not be cached.

嘿哥们儿 2024-12-22 07:27:35

您是在问如何使读取数据库或读取节点线程安全吗?

如果您尝试后者并且不经​​常写作,那么为什么不让您的节点 不可变,就这样?如果您需要编写某些内容,请从现有节点复制数据,修改它并创建另一个节点,然后将其放入数据库中。

Are you asking how to make reading the DB or reading the Nodes thread safe?

If you're trying to the latter and you're not writing very often, then why not make your nodes immutable, period? If you need to write something, then copy the data from the existing node, modify it and create another node which you can then put in your database.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文