lockless_queue.h
Go to the documentation of this file.
1 /*
2  * Copyright 2018 The Cartographer Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef CARTOGRAPHER_COMMON_LOCKLESS_QUEUE_H_
18 #define CARTOGRAPHER_COMMON_LOCKLESS_QUEUE_H_
19 
20 #include <atomic>
21 #include <memory>
22 
24 #include "glog/logging.h"
25 
26 namespace cartographer {
27 namespace common {
28 
29 // Lock-less queue which is thread safe for concurrent data producers and a
30 // single data consumer.
31 //
32 // This lockless queue implementation is adapted from
33 // https://github.com/resonance-audio/resonance-audio/blob/master/resonance_audio/utils/lockless_task_queue.h
34 template <typename T>
36  public:
38  free_list_head_ = nullptr;
39  incoming_data_list_head_ = nullptr;
40  data_list_head_ = nullptr;
41  data_list_tail_ = nullptr;
42  }
43 
45  FreeNodes(free_list_head_.exchange(nullptr));
46  FreeNodes(incoming_data_list_head_.exchange(nullptr));
48  }
49 
50  // Pushes the data item into the queue.
51  void Push(std::unique_ptr<T> t) {
52  Node* const free_node = PopNodeFromFreeList();
53  CHECK(free_node);
54  free_node->data = std::move(t);
56  }
57 
58  // Pops the oldest data item from the queue. If the queue is empty returns a
59  // nullptr.
60  std::unique_ptr<T> Pop() {
61  SwapLists();
62  if (data_list_head_ != nullptr) {
63  Node* node = data_list_head_;
65  std::unique_ptr<T> data = std::move(node->data);
67  return std::move(data);
68  }
69  return nullptr;
70  }
71 
72  private:
73  // Node to model a single-linked list.
74  struct Node {
75  Node() = default;
76 
77  // Dummy copy constructor to enable vector::resize allocation.
78  Node(const Node& node) : next() {}
79 
80  // User data.
81  std::unique_ptr<T> data;
82 
83  // Pointer to next node.
84  std::atomic<Node*> next;
85  };
86 
87  // Deallocates all nodes of the list starting with 'head'.
88  void FreeNodes(Node* node) {
89  while (node != nullptr) {
90  Node* next_node_ptr = node->next;
91  delete node;
92  node = next_node_ptr;
93  }
94  }
95 
96  // Pushes a node to the front of a list.
97  void PushNodeToList(std::atomic<Node*>* list_head, Node* node) {
98  DCHECK(list_head);
99  DCHECK(node);
100  Node* list_head_ptr;
101  do {
102  list_head_ptr = list_head->load();
103  node->next = list_head_ptr;
104  } while (!std::atomic_compare_exchange_strong_explicit(
105  list_head, &list_head_ptr, node, std::memory_order_release,
106  std::memory_order_relaxed));
107  }
108 
109  // Pops a node from the front of the free node list. If the list is empty
110  // constructs a new node instance.
112  Node* list_head_ptr;
113  Node* list_head_next_ptr;
114  do {
115  list_head_ptr = free_list_head_.load();
116  if (list_head_ptr == nullptr) {
117  return new Node;
118  }
119  list_head_next_ptr = list_head_ptr->next.load();
120  } while (!std::atomic_compare_exchange_strong_explicit(
121  &free_list_head_, &list_head_ptr, list_head_next_ptr,
122  std::memory_order_relaxed, std::memory_order_relaxed));
123  return list_head_ptr;
124  }
125 
126  // Swaps the incoming data list for an empty list and appends all items
127  // to 'data_list_tail_'.
128  void SwapLists() {
129  Node* node_itr = incoming_data_list_head_.exchange(nullptr);
130  if (node_itr == nullptr) {
131  // There is no data on the incoming list.
132  return;
133  }
134  // The first node of the incoming data list will become the tail of the
135  // data list.
136  Node* const data_list_tail = node_itr;
137 
138  // Reverses the list order. After this operation 'prev_node_itr' points to
139  // head of the new data list items.
140  Node* prev_node_itr = nullptr;
141  while (node_itr != nullptr) {
142  Node* const next_node_ptr = node_itr->next;
143  node_itr->next = prev_node_itr;
144  prev_node_itr = node_itr;
145  node_itr = next_node_ptr;
146  }
147 
148  // If the previous data list was empty, replace head rather than appending
149  // to the list.
150  if (data_list_tail_ == nullptr) {
151  data_list_head_ = prev_node_itr;
152  } else {
153  data_list_tail_->next = prev_node_itr;
154  }
155  data_list_tail_ = data_list_tail;
156  }
157 
158  // Pointer to head node of free list.
159  std::atomic<Node*> free_list_head_;
160 
161  // Pointer to head node of incoming data list, which is in FILO order.
162  std::atomic<Node*> incoming_data_list_head_;
163 
164  // Pointer to head node of data list.
166 
167  // Pointer to tail node of data list.
169 };
170 
171 } // namespace common
172 } // namespace cartographer
173 
174 #endif // CARTOGRAPHER_COMMON_LOCKLESS_QUEUE_H_
std::atomic< Node * > free_list_head_
void PushNodeToList(std::atomic< Node *> *list_head, Node *node)
void Push(std::unique_ptr< T > t)
std::atomic< Node * > incoming_data_list_head_


cartographer
Author(s): The Cartographer Authors
autogenerated on Mon Feb 28 2022 22:00:58