简体   繁体   English

惰性初始化缓存...我如何使其成为线程安全的?

[英]Lazy initialized caching… how do I make it thread-safe?

that's what I have: 那就是我所拥有的:

  • a Windows Service Windows服务
    • C# 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++ C ++
    • small enough to fit into memory 足够小以适合内存
    • big enough not wanting to load it on startup (eg 10GB) 足够大,不想在启动时加载它(例如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. 由于DB非常频繁地访问这些节点信息(每秒几千次),并且由于我不经常编写,所以我想使用某种双重检查的锁定模式。

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) 每个节点都存储一个键-值对列表(存储在文件中以实现持久性,文件大小大小:10kB)
  • when accessing a node for the first time, the list is loaded and stored in a map (sth. like std::map) 首次访问节点时,列表已加载并存储在地图中(例如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). 那是我需要线程安全的地方,它不应该花费太多时间,因为我通常不需要它(尤其是整个数据库都在内存中时)。

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. 因为在初始化指向它的Resource对象之前,有可能将资源指针分配给非空值。

But with the "atomics" feature of C++11 you may have a doubly checked locking mechanism. 但是,使用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() ensures that dummy will be first created then assigned to resource . MemoryBarrier()确保将首先创建dummy然后将其分配给resource According to this link pointer assignments will be atomic in x86 and x64 systems. 根据此链接,在x86和x64系统中,指针分配是原子的。 And volatile ensures that the value of resource will not be cached. volatile确保不会缓存resource的值。

Are you asking how to make reading the DB or reading the Nodes thread safe? 您是在问如何使读取DB或读取Nodes线程安全吗?

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. 如果需要编写某些内容,请从现有节点复制数据,进行修改并创建另一个节点,然后将其放入数据库中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM