Lock-free stack: details on implementation of Hazard Points, Part 2

0
7
Lock-free stack: details on implementation of Hazard Points, Part 2


In my previous article I started explaining the Hazard Points, which I continue in this post.




Rainer Grim has been working as a software architect, team and training manager for many years. He likes to write articles on programming languages ​​C ++, Python and Haskel, but also likes to speak at expert conferences. On his blog modern C ++, he deal intensively with his passion C ++.

RetireList Public members are tasks isInUse, addNode And deleteUnusedNodesAlso, it has internal class RetireNodeIT’s nuclear member and private member function addToRetiredNodes,

template >
class RetireList {

  struct RetiredNode {
    MyNode* node;
    RetiredNode* next;
    RetiredNode(MyNode* p) : node(p), next(nullptr) { }
    ~RetiredNode() {
      delete node;
    }
  };

  std::atomic RetiredNodes;
  void addToRetiredNodes(RetiredNode* retiredNode) {
    retiredNode->next = RetiredNodes.load();
    while (!RetiredNodes.compare_exchange_strong(retiredNode->next, retiredNode));
  }

  public:

  bool isInUse(MyNode* node) {
    for (std::size_t i = 0; i < MaxHazardPointers; ++i) {
      if (HazardPointers(i).pointer.load() == node) return true;
    }
    return false;
  }

  void addNode(MyNode* node) {
    addToRetiredNodes(new RetiredNode(node));
  }

  void deleteUnusedNodes() {
    RetiredNode* current = RetiredNodes.exchange(nullptr);
    while (current) {
      RetiredNode* const next = current->next;
      if (!isInUse(current->node)) delete current;
      else addToRetiredNodes(current);
      current = next;
    }
  }
};

Android version: combination with data security and convenience grapheneeosAndroid version: combination with data security and convenience grapheneeos

Let’s start with data type interface RetireListMember ceremony isInUse Check whether node is in use. This variable goes through the template HazardPointersIt is parameter on the data type of the node. HazardPointers Is a se-desert HazardPointer Length 50. One HazardPointer There are a atomic thread ID and a nuclear indicator on a knot:

constexpr std::size_t MaxHazardPointers = 50;

template >
struct HazardPointer {
  std::atomic<:thread::id> id;
  std::atomic pointer;
};

template 
HazardPointer HazardPointers(MaxHazardPointers);

Such as the use of an STL container std::set As HazardPointers It is very practical. std::set Already arranged and on average a continuous access guarantees time, but there is a major problem: it is not thread-safe.

Member ceremony addNode Takes a knot, a private member calls the function addToRetiredNodes And add knots to one RetiredNode One. RetiredNode There is a Raii object and guarantees that the attached knot will be destroyed, which leaves its memory. All retired nodes make a bus linked list.

Member ceremony deleteUnusedNodes The bus of retired knot goes through the following pattern in the linked list:

void deleteUnusedNodes() {
  RetiredNode* current = RetiredNodes.exchange(nullptr);
  while (current) {
    RetiredNode* const next = current->next;
    if (!isInUse(current->node)) delete current;
    else addToRetiredNodes(current);
    current = next;
  }
}

It checks the current knot, refers to the next knot current->next And updates the current knot with the next knot. Finally, the current knot is destroyed if it is no longer used or added to harmful nodes. Private member function addToRetireNodes Add only decayed knots to the linked list. To complete her task, she loads the dated nodes and creates a new knot retiredNode For the new head of the simple linked list.

First retiredNode The simple linked list becomes the new head, I have to make sure that it is still the head of the simple linked list, as another thread may come and change the head of the simple linked list. While thanks for the loop retiredNode Only when on the new head retiredNode->next = RetiredNodes.load() come into force. If not, it becomes retiredNode->next But RetiredNodes.load() Updated.

Only one part of the puzzle is missing: the owner of the dangerous indicator:

template >
class HazardPointerOwner {

  HazardPointer* hazardPointer;

public:
  HazardPointerOwner(HazardPointerOwner const &) = delete;
  HazardPointerOwner operator=(HazardPointerOwner const &) = delete;

  HazardPointerOwner() : hazardPointer(nullptr) {
    for (std::size_t i = 0; i < MaxHazardPointers; ++i) {
      std::thread::id old_id;
      if (HazardPointers(i).id.compare_exchange_strong(
          old_id, std::this_thread::get_id())) {
        hazardPointer = &HazardPointers(i);
        break;
      }
    }
    if (!hazardPointer) {
      throw std::out_of_range(„No hazard pointers available!“);
    }
  }

  std::atomic& getPointer() {
    return hazardPointer->pointer;
  }

  ~HazardPointerOwner() {
    hazardPointer->pointer.store(nullptr);
    hazardPointer->id.store(std::thread::id());
  }
};

HazardPointerOwner Catchs one HazardPointerIt is in the constitution through everyone HazardPointer set. Call compare_exchange_strong Check in an atomic step whether the current has been passed HazardPointer Not set, and now depends on the ID of the thread made (std::this_thread::get_id()In success HazardPointer To new HazardPointerIt is returned to the customer that the member works getPointer Call. When all indicate the danger HazardPointers Constructor solves std::out_of_range Exception Outside. Finally HazardPointerOwner hazardPointer Back to your standard state.

In C ++ 26 we will get a danger indicator. I will write about it in my next article.


(RME)

Lock-free stack: details on implementation of Hazard Points, Part 2Lock-free stack: details on implementation of Hazard Points, Part 2

LEAVE A REPLY

Please enter your comment!
Please enter your name here