Honeycomb  0.1
Component-Model Framework
Queue.h
Go to the documentation of this file.
1 // Honeycomb, Copyright (C) 2015 NewGamePlus Inc. Distributed under the Boost Software License v1.0.
2 #pragma once
3 
5 
6 namespace honey { namespace lockfree
7 {
8 
10 
13 template<class T>
15 {
16  typedef typename FreeList<T>::TaggedHandle TaggedHandle;
17 
18  struct Node
19  {
20  //init without overwriting previous tag
21  template<class T_>
22  Node(T_&& val) :
23  val(forward<T_>(val))
24  {
25  next.store(TaggedHandle(nullptr, next.load(atomic::Order::relaxed).nextTag()), atomic::Order::relaxed);
26  }
27 
28  T val;
30  };
31 
32  typedef FreeList<Node> FreeList;
33 
34 public:
35  typedef T value_type;
36 
38  _freeList(capacity),
39  _head(TaggedHandle(_freeList.handle(_freeList.construct(T())), 0)),
40  _tail(_head),
41  _size(0) {}
42 
43  ~Queue() { clear(); _freeList.destroy(_freeList.deref(_head)); }
44 
46  void reserve(szt capacity) { _freeList.reserve(capacity); }
48  szt capacity() const { return _freeList.capacity(); }
49 
51  template<class T_>
52  void push(T_&& val)
53  {
54  Node* node = _freeList.construct(forward<T_>(val));
55 
56  TaggedHandle tail;
57  while (true)
58  {
59  tail = _tail.load(atomic::Order::acquire);
60  TaggedHandle next = _freeList.deref(tail)->next.load(atomic::Order::acquire);
61  //ensure that tail and next are consistent
62  if (tail != _tail.load(atomic::Order::acquire)) continue;
63  //check if tail isn’t last
64  if (next)
65  {
66  //tail isn’t at the last element, try to move tail forward
67  _tail.cas(TaggedHandle(next, tail.nextTag()), tail);
68  continue;
69  }
70  //try to add the element onto the end of the list
71  if (_freeList.deref(tail)->next.cas(TaggedHandle(_freeList.handle(node), next.nextTag()), next)) break; //success
72  }
73  //try to relocate tail to the inserted element
74  _tail.cas(TaggedHandle(_freeList.handle(node), tail.nextTag()), tail);
75  ++_size;
76  }
77 
79  bool pop(optional<T&> val = optnull)
80  {
81  TaggedHandle head;
82  while (true)
83  {
84  head = _head.load(atomic::Order::acquire);
85  TaggedHandle tail = _tail.load(atomic::Order::acquire);
86  TaggedHandle next = _freeList.deref(head)->next.load(atomic::Order::acquire);
87  //ensure that head, tail and next are consistent
88  if (head != _head.load(atomic::Order::acquire)) continue;
89  //check if queue is empty or tail isn’t last
90  if (head.handle() == tail)
91  {
92  if (!next) return false; //queue is empty
93  //tail isn’t at the last element, try to move tail forward
94  _tail.cas(TaggedHandle(next, tail.nextTag()), tail);
95  continue;
96  }
97  //ensure we have a next, it's possible that the list was empty and then changed before reading tail
98  if (!next) continue;
99  //tail is in position, read the value before cas, otherwise another pop can destroy next
100  if (val) val = _freeList.deref(next)->val; //copy val (not move) as there may be concurrent pop attempts
101  //try to move head forward
102  if (_head.cas(TaggedHandle(next, head.nextTag()), head)) break; //success
103  }
104  --_size;
105 
106  _freeList.destroy(_freeList.deref(head));
107  return true;
108  }
109 
111  bool front(T& val)
112  {
113  while (true)
114  {
115  TaggedHandle head = _head.load(atomic::Order::acquire);
116  TaggedHandle tail = _tail.load(atomic::Order::acquire);
117  TaggedHandle next = _freeList.deref(head)->next.load(atomic::Order::acquire);
118  //ensure that head, tail and next are consistent
119  if (head != _head.load(atomic::Order::acquire)) continue;
120  //check if queue is empty
121  if (head.handle() == tail && !next) return false;
122  //ensure we have a next, it's possible that the list was empty and then changed before reading tail
123  if (!next) continue;
124  //ensure val we read is consistent with head, otherwise we could end up returning a destroyed value
125  val = _freeList.deref(next)->val;
126  if (head == _head.load(atomic::Order::acquire)) return true;
127  }
128  }
129 
131  bool back(T& val)
132  {
133  while (true)
134  {
135  TaggedHandle head = _head.load(atomic::Order::acquire);
136  TaggedHandle tail = _tail.load(atomic::Order::acquire);
137  TaggedHandle next = _freeList.deref(tail)->next.load(atomic::Order::acquire);
138  //ensure that tail and next are consistent
139  if (tail != _tail.load(atomic::Order::acquire)) continue;
140  //check if tail isn’t last
141  if (next)
142  {
143  //tail isn’t at the last element, try to move tail forward
144  _tail.cas(TaggedHandle(next, tail.nextTag()), tail);
145  continue;
146  }
147  //check if queue is empty
148  if (head.handle() == tail) return false;
149  //ensure val we read is consistent with head and tail, otherwise we could end up returning a destroyed value
150  val = _freeList.deref(tail)->val;
151  if (head == _head.load(atomic::Order::acquire) &&
152  tail == _tail.load(atomic::Order::acquire)) return true;
153  }
154  }
155 
157  void clear() { while (pop()); }
158 
160  bool empty() const { return !_size; }
162  szt size() const { return _size; }
163 
164 private:
165  FreeList _freeList;
166  Atomic<TaggedHandle> _head;
167  Atomic<TaggedHandle> _tail;
168  Atomic<szt> _size;
169 };
170 
171 } }
~Queue()
Definition: Queue.h:43
bool front(T &val)
Get a copy of the next element that will be popped. Returns true on success, false if there is no ele...
Definition: Queue.h:111
Holds block handle and tag to prevent lock-free ABA issues.
Definition: Pool.h:95
T * construct(Args &&...args)
Construct object and remove from free list.
Definition: FreeList.h:35
Queue(szt capacity=0)
Definition: Queue.h:37
void reserve(szt capacity)
Ensure that enough storage is allocated for a number of objects.
Definition: FreeList.h:27
static optnull_t optnull
Null optional, use to reset an optional to an uninitialized state or test for initialization.
Definition: Optional.h:12
const Handle & handle() const
Definition: Pool.h:105
bool empty() const
Check whether the queue does not contain any elements.
Definition: Queue.h:160
Inherit to declare that class is not copyable.
Definition: Meta.h:286
szt capacity() const
The number of elements for which storage is allocated.
Definition: Queue.h:48
T value_type
Definition: Queue.h:35
Definition: Atomic.h:119
Must be a load op. Synchronize with a prior release in another thread.
No order constraint, same as plain load/store. Unsafe but best performance.
void reserve(szt capacity)
Ensure that enough storage is allocated for a number of elements.
Definition: Queue.h:46
size_t szt
Size type, shorthand for size_t.
Definition: Core.h:90
void destroy(T *ptr)
Destroy object and add to free list.
Definition: FreeList.h:40
bool pop(optional< T & > val=optnull)
Remove oldest element from the queue and copy it into val. Returns true on success, false if there is no element to pop.
Definition: Queue.h:79
void clear()
Remove all elements.
Definition: Queue.h:157
szt size() const
The number of elements in the queue.
Definition: Queue.h:162
szt capacity() const
The number of objects for which storage is allocated.
Definition: FreeList.h:29
Specialization for references.
Definition: Optional.h:132
Lock-free FIFO queue. Uses auto-expanding freelist allocator so memory is only reclaimed upon destruc...
Definition: Queue.h:14
void push(T_ &&val)
Add new element constructed with val onto the end of the queue.
Definition: Queue.h:52
Int nextTag() const
Definition: Pool.h:107
T * deref(Handle handle) const
Get object from compressed handle.
Definition: FreeList.h:45
bool back(T &val)
Get a copy of the last element. Returns true on success, false if there is no element.
Definition: Queue.h:131
Handle handle(T *ptr) const
Get compressed handle for object.
Definition: FreeList.h:43
Global Honeycomb namespace.