CMU-15445project0(仅自己记录留念)

一、代码解读

1.1 trie.h整体代码解读

#pragma once

#include <algorithm>
#include <cstddef>
#include <future>  // NOLINT
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

namespace bustub {

/// A special type that will block the move constructor and move assignment operator. Used in TrieStore tests.
class MoveBlocked {
 public:
  explicit MoveBlocked(std::future<int> wait) : wait_(std::move(wait)) {}

  MoveBlocked(const MoveBlocked &) = delete;
  MoveBlocked(MoveBlocked &&that) noexcept {
    if (!that.waited_) {
      that.wait_.get();
    }
    that.waited_ = waited_ = true;
  }

  auto operator=(const MoveBlocked &) -> MoveBlocked & = delete;
  auto operator=(MoveBlocked &&that) noexcept -> MoveBlocked & {
    if (!that.waited_) {
      that.wait_.get();
    }
    that.waited_ = waited_ = true;
    return *this;
  }

  bool waited_{false};
  std::future<int> wait_;
};

// A TrieNode is a node in a Trie.
class TrieNode {
 public:
  // Create a TrieNode with no children.
  TrieNode() = default;

  // Create a TrieNode with some children.
  explicit TrieNode(std::map<char, std::shared_ptr<const TrieNode>> children) : children_(std::move(children)) {}

  virtual ~TrieNode() = default;

  // Clone returns a copy of this TrieNode. If the TrieNode has a value, the value is copied. The return
  // type of this function is a unique_ptr to a TrieNode.
  //
  // You cannot use the copy constructor to clone the node because it doesn't know whether a `TrieNode`
  // contains a value or not.
  //
  // Note: if you want to convert `unique_ptr` into `shared_ptr`, you can use `std::shared_ptr<T>(std::move(ptr))`.
  virtual auto Clone() const -> std::unique_ptr<TrieNode> { return std::make_unique<TrieNode>(children_); }

  // A map of children, where the key is the next character in the key, and the value is the next TrieNode.
  // You MUST store the children information in this structure. You are NOT allowed to remove the `const` from
  // the structure.
  std::map<char, std::shared_ptr<const TrieNode>> children_;

  // Indicates if the node is the terminal node.
  bool is_value_node_{false};

  // You can add additional fields and methods here except storing children. But in general, you don't need to add extra
  // fields to complete this project.
};

// A TrieNodeWithValue is a TrieNode that also has a value of type T associated with it.
template <class T>
class TrieNodeWithValue : public TrieNode {
 public:
  // Create a trie node with no children and a value.
  explicit TrieNodeWithValue(std::shared_ptr<T> value) : value_(std::move(value)) { this->is_value_node_ = true; }

  // Create a trie node with children and a value.
  TrieNodeWithValue(std::map<char, std::shared_ptr<const TrieNode>> children, std::shared_ptr<T> value)
      : TrieNode(std::move(children)), value_(std::move(value)) {
    this->is_value_node_ = true;
  }

  // Override the Clone method to also clone the value.
  //
  // Note: if you want to convert `unique_ptr` into `shared_ptr`, you can use `std::shared_ptr<T>(std::move(ptr))`.
  auto Clone() const -> std::unique_ptr<TrieNode> override {
    return std::make_unique<TrieNodeWithValue<T>>(children_, value_);
  }

  // The value associated with this trie node.
  std::shared_ptr<T> value_;
};

// A Trie is a data structure that maps strings to values of type T. All operations on a Trie should not
// modify the trie itself. It should reuse the existing nodes as much as possible, and create new nodes to
// represent the new trie.
//
// You are NOT allowed to remove any `const` in this project, or use `mutable` to bypass the const checks.
class Trie {
 private:
  // The root of the trie.
  std::shared_ptr<const TrieNode> root_{nullptr};

  // Create a new trie with the given root.
  explicit Trie(std::shared_ptr<const TrieNode> root) : root_(std::move(root)) {}

 public:
  // Create an empty trie.
  Trie() = default;

  // Get the value associated with the given key.
  // 1. If the key is not in the trie, return nullptr.
  // 2. If the key is in the trie but the type is mismatched, return nullptr.
  // 3. Otherwise, return the value.
  template <class T>
  auto Get(std::string_view key) const -> const T *;

  // Put a new key-value pair into the trie. If the key already exists, overwrite the value.
  // Returns the new trie.
  template <class T>
  auto Put(std::string_view key, T value) const -> Trie;

  // Remove the key from the trie. If the key does not exist, return the original trie.
  // Otherwise, returns the new trie.

  auto Remove(std::string_view key) -> Trie;

  // Get the root of the trie, should only be used in test cases.
  auto GetRoot() const -> std::shared_ptr<const TrieNode> { return root_; }
};

}  // namespace bustub

这段代码是一个用C++编写的头文件,它定义了一个名为bustub的命名空间中的几个类,这些类共同实现了一个Trie(前缀树或字典树)数据结构。下面是对这个头文件中的主要组成部分的概述:预处理指令和库包含:#pragma once: 这是一个常用的预处理指令,确保头文件内容只被包含(编译)一次,避免重复包含时的冲突。包含了一系列标准库头文件,如, , , 等等,这些库提供了用于实现Trie所需的数据结构和功能。
1.命名空间 bustub: 代码封装在bustub命名空间内,以避免命名冲突
2.类 MoveBlocked: 一个特殊类型,用于阻止移动构造和移动赋值操作。在Trie的测试中使用,以确保对象不会被意外移动
3.**类 TrieNode:**代表Trie中的一个节点。提供构造函数来创建有或无子节点的Trie节点。包含一个虚拟的Clone方法,用于复制这个Trie节点。有一个表示子节点的映射和一个标志是否为值节点的bool成员。
4.**模板类 TrieNodeWithValue:**继承自TrieNode,代表一个同时存储值的Trie节点。提供了构造函数来创建带值的Trie节点。重写了Clone方法,以确保值也被复制。
5.**类 Trie:**表示一个映射字符串到类型T值的Trie数据结构。提供操作方法,如获取值、插入键值对和移除键。包含一个指向根节点的智能指针。

1.2 trie.h具体代码解释

因为自己开始c++基础比较薄弱,所以针对一些没有见过的代码格式进行了了解,下面是几个当时没理解的代码的相关论述:
1. auto Clone() const -> std::unique_ptr<TrieNode> override { return std::make_unique<TrieNodeWithValue<T>>(children_, value_); }
auto: 这是一个自动类型推断关键字。在这里,它用于表示函数的返回类型将在箭头(->)之后指定。
Clone(): 这是函数的名称。Clone函数通常用于创建对象的一个副本。
const: 这表明该函数不会修改类的任何成员变量,即它是一个只读函数。
-> std::unique_ptr: 这是尾置返回类型语法,用于明确指定函数的返回类型。在这里,函数返回一个std::unique_ptr,这是一个智能指针,指向TrieNode或其派生类的对象。
函数体:
return std::make_unique<TrieNodeWithValue>(children_, value_);: 这个语句创建并返回一个TrieNodeWithValue类型的对象的唯一指针。这个对象是当前对象的一个副本,因为它使用当前对象的children_和value_成员进行初始化。std::make_unique是C++14中引入的一个函数,用于创建一个唯一指针(unique_ptr),这是一种更安全、更简洁的方式来创建动态分配的对象。
2.template <class T>
用于定义一个类或函数,那么这个类或函数就可以适用于多种类型的T。当实例化这个模板时(即在代码中使用它时),T将被具体的类型替换,例如TrieNodeWithValue或TrieNodeWithValuestd::string,这取决于使用者指定的具体类型。
3.explicit TrieNodeWithValue(std::shared_ptr<T> value) : value_(std::move(value)) { this->is_value_node_ = true; }
explicit: 这个关键字用于构造函数,表示该构造函数禁止隐式类型转换。这意味着你不能通过赋值或其他隐式方式创建TrieNodeWithValue对象;必须直接调用构造函数。
std::move(value): 这里使用了std::move,将value参数转换为右值引用,允许在初始化value_时使用移动语义。这可以提高性能,尤其是对于资源密集型对象。
this->is_value_node_ = true;: 这个语句设置类的另一个成员变量is_value_node_为true。这可能表示当前节点是一个包含值的节点(即非仅用于结构的节点)。
4.TrieNodeWithValue(std::map<char, std::shared_ptr<const TrieNode>> children, std::shared_ptr<T> value) : TrieNode(std::move(children)), value_(std::move(value)) { this->is_value_node_ = true; }
std::map<char, std::shared_ptr> children: 第一个参数,表示这个Trie节点的子节点,其中键是字符类型,值是指向const TrieNode的共享指针。
std::shared_ptr value: 第二个参数,表示与这个Trie节点相关联的值,类型为T的共享指针。

1.3智能指针、dynamic_pointer_cast、以及const_pointer_cast

1.3.1智能指针

首先要知道什么是智能指针,智能指针的作用。
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
这里主要涉及到shared_ptr:共享指针、unique_ptr:唯一指针,其他还包括两种weak_ptr和auto_ptr(C++17中已经被弃用),正好看到了智能指针,就对前三种指针进行一个概述:

**1.shared_ptr:**一种共享式智能指针,它可以被多个指针共享同一个资源,资源的引用计数会被自动管理,当所有的shared_ptr对象都不再需要该资源时,资源会自动被销毁。
**2.unique_ptr:**一种独占式智能指针,它确保了只有一个指针可以指向资源,可以通过std::move()函数将资源的所有权转移给其他unique_ptr对象。它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
**3.weak_ptr:**一种弱引用智能指针,它可以被多个weak_ptr对象共享,但它不会增加资源的引用计数,当所有的shared_ptr对象都不再需要该资源时,weak_ptr对象会自动失效。弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。 通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放;通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象;通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源。

1.3.2 dynamic_pointer_cast

1.std::dynamic_pointer_cast<目标类型>(被转换智能指针)
接受一个被转换的智能指针和目标类型的指针类型作为参数,并返回一个新的智能指针。dynamic_pointer_cast只适用于std::shared_ptr和std::weak_ptr类型的智能指针,用于进行智能指针的动态类型转换。
2.与dynamic_cast的区别
适用对象类型:dynamic_pointer_cast适用于智能指针类型,如std::shared_ptr和std::weak_ptr,用于进行智能指针的动态类型转换。而dynamic_cast适用于指针和引用类型,用于进行指针或引用类型的动态类型转换。
用法:dynamic_pointer_cast是一个函数模板,接受两个参数,分别是要转换的指针和目标类型的指针类型。它返回一个智能指针类型的结果。而dynamic_cast是一个运算符,在使用时需要使用dynamic_cast<目标类型>(被转换对象)的语法。它返回一个指针或引用类型的结果。
转换失败处理:dynamic_pointer_cast在转换失败时,会返回一个空智能指针(nullptr)。而dynamic_cast在转换失败时,如果转换是指针类型,会返回一个空指针(nullptr);如果转换是引用类型,会抛出std::bad_cast异常。
适用范围:dynamic_pointer_cast只适用于智能指针类型,因此只能用于具有动态多态性的对象。而dynamic_cast可以用于具有动态多态性的对象,也可以用于普通的指针和引用类型。

1.3.2 const_pointer_cast

这里与上面的dynamic_pointer_cast一样的规则,这里对几种常见的进行汇总:
1.const_cast
**主要用途:**对于常量指针,移除对象的常量性。
转换格式:const_cast < type-name > (expression)
要求:type-name和expression必须是同一种类型,他们只在const和volatile上存在区别,转换成功就赋值就行,转换失败则返回0。
volatile:提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
与常规类型转换的不同:常规类型转换可以转换为不同类型,
但是const_cast不允许不同类型的转换
基本原则:不允许将定义为const的变量转换为非const类型(这个是针对非指针)。
分几种情况:其实就是基本原则的体现
原来就是常量,用一个常量指针指向它,要将这个常量指针转换为非常量指针并修改其值,这是不允许的。
原来不是常量,用一个常量指针指向它,要将这个常量指针转换为非常量指针并修改其值,这是允许的。

2.static_cast
格式:static_cast < type-name > (expression)
当且仅当type-name类型可以隐式转换为expression类型,或反之,否则类型转换会报错,两者能互相转换
允许的转换
允许向上转换(继承类指针转换为基类指针)
允许向下转换(基类指针转换为继承类指针)
由于枚举类可以隐式转换为int,因此static_cast允许这类转换,并且允许将int类型转换为枚举类
允许将double转换为int,将int转换为double
允许将float转换为long,允许将long转换为float
不允许的转换
不同类型的指针之间互相转换
非指针类型和指针类型之间的相互转换
不同类型的引用之间的转换

3.reinterpret_cast
格式:reinterpret_cast < type-name > (expression)
转换时,执行的是逐个比特复制的操作。转换的安全性由程序员负责。
允许的转换:
可以将指针类型转换为能够容纳该指针类型的整型,反之不能转换。
不同类型的指针之间的转换
不同类型的引用之间的转换
不允许的转换:
不能将函数指针转换为数据指针
不允许的转换:
不能将函数指针转换为数据指针

1.4 具体project代码实现

1.4.1 task1

这个任务关键在于理解trie的结构,他最关键的在于需要对每个节点进行深拷贝,并且保证尽可能多的复用原来trie的节点,理解了trie的结构,也就大概能知道实现的思路。

1.4.1.1 GET方法(task1)
template <class T>
auto Trie::Get(std::string_view key) const -> const T * {
  //从根节点开始
  auto node= root_;
  //沿着键中字符遍历Trie
  for (char ch : key) {
    //如果当前节点为nullptr,或者没有对应字符的子节点,返回nullptr
    // 这意味着键不在Trie中
    if (node == nullptr || node ->children_.find(ch) == node->children_.end()) {
      return nullptr;
    }
    //移动到对应当前字符的子结点
    node = node->children_.at(ch);
  }

  //检查最终节点是否为值节点并且类型匹配
  // 如果是,尝试将节点转换为TrieNodeWithValue<T>类型
  if (node != nullptr && node->is_value_node_) {
    const auto *value_node = dynamic_cast<const TrieNodeWithValue<T>*>(node.get());
    // 如果转换 成功并且节点包含正确类型的值,则返回该值的指针
    if (value_node != nullptr) {
      return value_node->value_.get();
    }
  }
  // 如果键未找到或者类型不匹配,返回nullptr
  return nullptr;
  // You should walk through the trie to find the node corresponding to the key. If the node doesn't exist, return
  // nullptr. After you find the node, you should use `dynamic_cast` to cast it to `const TrieNodeWithValue<T> *`. If
  // dynamic_cast returns `nullptr`, it means the type of the value is mismatched, and you should return nullptr.
  // Otherwise, return the value.
}

GET函数比较简单,只需要遍历,return正确值即可。

1.4.1.2 PUT方法(task1)
template <class T>
auto Trie::Put(std::string_view key, T value) const -> Trie {
  std::shared_ptr<const TrieNode> grandparent = nullptr;
  std::shared_ptr<const TrieNode> root_node = root_ ? root_->Clone() : nullptr;
  std::shared_ptr<const TrieNode> parent = nullptr;
  char ch = 0;
  auto node =root_node;
  //key=null
  if (key.empty()) {
    // 处理空字符串键
    auto mutableNode = std::const_pointer_cast<TrieNode>(root_node);
    auto valueNode = std::dynamic_pointer_cast<TrieNodeWithValue<T>>(mutableNode);
    if (valueNode) {
      // 如果根节点已经是一个值节点,更新其值
      valueNode->value_ = std::make_shared<T>(std::move(value));
    } else {
      // 否则,创建一个新的值节点
      auto newvalue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
      root_node = newvalue;
    }
    return Trie(root_node);
  }
  // 遍历键中的每个字符
  for (size_t i = 0; i < key.size(); ++i) {

      ch = key[i];

    if (node && node->children_.find(ch) != node->children_.end()) {
      // 移动到对应的子节点
      parent = node;
      grandparent =node;
      std::unique_ptr<TrieNode> cloned_node = node->children_.at(ch)->Clone();
      node = std::shared_ptr<TrieNode>(std::move(cloned_node));
      auto par = std::const_pointer_cast<TrieNode>(parent);
      auto grand_par = std::const_pointer_cast<TrieNode>(grandparent);
      par->children_[ch] = node;
      grand_par->children_[ch] = node;
    } else {
      // 创建新节点
      auto newNode = std::make_shared<const TrieNode>(std::map<char, std::shared_ptr<const TrieNode>>());
      grandparent =node;
      node = newNode;
      auto mutableParent = std::const_pointer_cast<TrieNode>(parent);//非值节点
      auto mutable_Parent = std::const_pointer_cast<TrieNode>(grandparent);//值节点
      if (parent) {
        // 更新父节点的子节点映射
        if(mutableParent->is_value_node_ && mutable_Parent->is_value_node_) {
          mutable_Parent->children_[ch] = newNode;
          auto valueNode = std::dynamic_pointer_cast<const TrieNodeWithValue<T>>(mutable_Parent);
          std::make_shared<const TrieNodeWithValue<T>>(mutable_Parent->children_, valueNode->value_);

        }else if(mutableParent->is_value_node_){
          mutableParent->children_[ch] = newNode;
          auto valueNode = std::dynamic_pointer_cast<const TrieNodeWithValue<T>>(mutableParent);
          std::make_shared<const TrieNodeWithValue<T>>(mutableParent->children_, valueNode->value_);
        }else{
          mutable_Parent->children_[ch] = newNode;
        }
      } else {
        // 更新根节点
        if (root_ == nullptr) {
          // 根节点为空,创建新的根节点
          auto new_Node = std::make_shared<const TrieNode>(std::map<char, std::shared_ptr<const TrieNode>>());
          auto root_child = std::const_pointer_cast<TrieNode>(new_Node);
          root_child->children_[ch] = newNode;
          root_node = new_Node;
          node = newNode;
          parent = new_Node;
          grandparent = new_Node;
        } else {
          // 根节点不为空,复制根节点以确保不影响现有节点
          std::unique_ptr<TrieNode> cloned_node = root_->Clone();
          auto newRoot = std::shared_ptr<TrieNode>(std::move(cloned_node));
          newRoot->children_[ch] = newNode; // 添加新的映射
          root_node = newRoot; // 设置新的根节点
        }

      }
      parent =node;
    }
  }
  // 在最终的节点处设置值

  if (node) {
    auto mutableNode = std::const_pointer_cast<TrieNode>(node);
    if (mutableNode->is_value_node_) {
      // 如果最后一个节点是值节点,更新其值
      auto valueNode = std::dynamic_pointer_cast<TrieNodeWithValue<T>>(mutableNode);
      if(valueNode){
        valueNode->value_ = std::make_shared<T>(std::move(value));
      } else{
        auto newvalue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
        std::const_pointer_cast<TrieNode>(grandparent)->children_[ch] = newvalue;
      }

    } else {
      // 如果最后一个节点是非值节点,转换为值节点并赋值
      auto newNodeWithValue = std::make_shared<TrieNodeWithValue<T>>(mutableNode->children_, std::make_shared<T>(std::move(value)));
      if (grandparent) {
        std::const_pointer_cast<TrieNode>(grandparent)->children_[ch] = newNodeWithValue;
      } else {
        root_node = newNodeWithValue;
      }
    }
  }
  return Trie(root_node);
}

PUT函数我觉得是最难的,因为要考虑值节点的与非值节点的变化,还有就是每一次的put都是在新的trie中进行,也就是要每遍历一个节点,都需要对该节点进行深拷贝,clone之后,也必须确保该节点与父节点的连接,另外对于空trie,第一次插入时,需要创造根节点等,思路不难,就是实现的时候,bug很多,需要不断的修改,加上c++能力薄弱,实现起来更加吃力。

1.4.1.3 REMOVE方法(task1)
auto Trie::Remove(std::string_view key) -> Trie {

  // 辅助函数,用于递归地移除键
  // 辅助函数,用于递归地移除键
  std::shared_ptr<const TrieNode> grandparent = nullptr;
  std::shared_ptr<const TrieNode> root_node = root_ ? root_->Clone() : nullptr;
  std::shared_ptr<TrieNode> newRoot = std::const_pointer_cast<TrieNode>(root_node);
  std::shared_ptr<const TrieNode> parent = nullptr;
  auto node =root_node;
  char ch = 0;
  std::vector<std::pair<std::shared_ptr<TrieNode>, char>> path;
  // 遍历键中的每个字符
  for (size_t i = 0; i < key.size(); ++i) {
    ch = key[i];
    auto mutableNode = std::const_pointer_cast<TrieNode>(node);
    auto it = node->children_.find(ch);
    if (it != node->children_.end()) {
      // 将当前节点和字符加入路径
      path.emplace_back(mutableNode, ch);
      parent = node;
      std::unique_ptr<TrieNode> cloned_node = node->children_.at(ch)->Clone();
      node = std::shared_ptr<TrieNode>(std::move(cloned_node));
      auto parNode = std::const_pointer_cast<TrieNode>(parent);
      parNode->children_[ch] = node;
    } else {
      // 如果当前字符不在子节点中,无法继续移除
      return Trie(root_);
    }
  }
  // 到达键的末尾,如果当前节点是值节点,移除该值节点
  if (node->is_value_node_) {
    auto mutableNode = std::const_pointer_cast<TrieNode>(node);
    mutableNode->is_value_node_ = false;
    node = std::make_shared<const TrieNode>(node->children_);
    auto parNode = std::const_pointer_cast<TrieNode>(parent);
    if(parent){
      parNode->children_[ch] = node;
    }else{
      return Trie(node);
    }
  } else {
    // 如果不是值节点,无法继续移除
    return Trie(root_);
  }
  // 从路径中回溯并更新父节点的子节点
  for (int i = path.size() - 1; i >= 0; --i) {
    auto [innerParent, innerCh] = path[i];
    auto child = innerParent->children_[innerCh];
    if (!child->is_value_node_ && child->children_.empty()) {
      // 如果子节点不是值节点且没有子节点,从父节点中移除该子节点
      innerParent->children_.erase(innerCh);
    } else {
      // 否则,停止回溯
      break;
    }
  }
  if(newRoot->children_.empty()){
    return Trie(nullptr);
  }
  return Trie(newRoot);
}

REMOVE方法实现逻辑也不难,需要注意的是,移除值节点后,要判断其后续以及父节点等是否为值节点,如果父节点不是值节点,需要同时移除这些非值节点。同时也需要进行clone深拷贝,在新的trie上进行操作。
!!!需要注意的是:这里的PUT和REMOVE函数,我第一反应是用递归更加方便,但是递归有个问题是,需要对每个节点进行深拷贝,同时要保证父节点与子结点的连接,但是因为新插入的节点可能与已有的值节点值类型不一样,导致在进行节点间映射的时候,导致无法正常进行,所以在这里我换成了非递归的形式。

1.4.2 task2:Concurrent Key-Value Store

在这里完全不用读写锁,只需要两把互斥锁,但得益于可持久化字典树每一次 Put、Remove 都会生成一个新版本的字典树,我们天然地解决了读写者的问题。因为每一个线程的 Put、Remove 都是对新树操作,完全不会影响到别的线程上的 Get,Get 仍在读旧版本的字典树。

class TrieStore {
 public:
  // This function returns a ValueGuard object that holds a reference to the value in the trie. If
  // the key does not exist in the trie, it will return std::nullopt.
  template <class T>
  auto Get(std::string_view key) -> std::optional<ValueGuard<T>>;

  // This function will insert the key-value pair into the trie. If the key already exists in the
  // trie, it will overwrite the value.
  template <class T>
  void Put(std::string_view key, T value);

  // This function will remove the key-value pair from the trie.
  void Remove(std::string_view key);

 private:
  // This mutex protects the root. Every time you want to access the trie root or modify it, you
  // will need to take this lock.
  std::mutex root_lock_;

  // This mutex sequences all writes operations and allows only one write operation at a time.
  std::mutex write_lock_;

  // Stores the current root for the trie.
  Trie root_;
};

}  // namespace bustub

这是该task的.h文件主要结构,这里的GET、PUT、REMPOVE具体操作都是调用task1中的具体函数,这里只需要对其进行相关加锁操作即可。
这个TrieStore 类就是对 Trie 的一个包装,含有两把锁,root_lock_ 用于 Get ,write_lock_ 用于 Put 和 Remove

1.4.2.1 GET
template <class T>
auto TrieStore::Get(std::string_view key) -> std::optional<ValueGuard<T>> {
  // Pseudo-code:
  // (1) Take the root lock, get the root, and release the root lock. Don't lookup the value in the
  //     trie while holding the root lock.
  // (2) Lookup the value in the trie.
  // (3) If the value is found, return a ValueGuard object that holds a reference to the value and the
  //     root. Otherwise, return std::nullopt.
  std::shared_ptr<const TrieNode> root_copy;
  auto trie = Trie();
  {
    // 使用互斥锁来保护根节点的读取
    std::lock_guard<std::mutex> lock(root_lock_);
    trie = root_;;  // 获取Trie的根节点
  }
  // 在Trie中查找键
  const T* value = trie.Get<T>(key);  // 假设TrieNode有Get方法
  // 根据查找结果返回
  if (value) {
    return ValueGuard<T>(trie, *value);
  } else {
    return std::nullopt;
  }
}

因为读并不会修改 Trie,Get 时没有必要上读锁,只需要用root_lock_即可。

1.4.2.1 GET
template <class T>
void TrieStore::Put(std::string_view key, T value) {
  std::shared_ptr<const TrieNode> root_copy;
  std::lock_guard<std::mutex> lock(write_lock_);

  // 复制当前根节点,对其进行修改
  Trie new_trie = root_.Put<T>(key, std::move(value)); // 假设Trie有Put方法
  {
    std::lock_guard<std::mutex> root_lock(root_lock_);
    root_ = new_trie;
  }
  // You will need to ensure there is only one writer at a time. Think of how you can achieve this.
  // The logic should be somehow similar to `TrieStore::Get`.
}

put函数需要对其进行写加锁。

1.4.2.3 REMOVE
void TrieStore::Remove(std::string_view key) {
  // You will need to ensure there is only one writer at a time. Think of how you can achieve this.
  // The logic should be somehow similar to `TrieStore::Get`.
  std::lock_guard<std::mutex> lock(write_lock_); // 获取写锁

  // 执行删除操作并获取新的 Trie 实例
  Trie new_trie_version = root_.Remove(key); // 假设 Trie 有 Remove 方法

  // 更新 Trie 的根节点
  {
    std::lock_guard<std::mutex> root_lock(root_lock_);
    root_ = new_trie_version;
  }
}

同样,因为涉及到对trie的修改,所以需要对其加写锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值