Honeycomb  0.1
Component-Model Framework
SpscDeque.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 
15 template<class T, class Alloc_ = typename DefaultAllocator<T>::type>
17 {
18 public:
19  typedef T value_type;
20  typedef typename Alloc_::template rebind<T>::other Alloc;
21 
22  SpscDeque(szt size = 0, const T& initVal = T(), const Alloc& alloc = Alloc()) :
23  _alloc(alloc),
24  _data(nullptr, finalize<T, Alloc>()),
25  _capacity(0),
26  _size(0),
27  _head(0),
28  _tail(0)
29  {
30  resize(size, initVal);
31  }
32 
33  ~SpscDeque() { clear(); }
34 
36  void resize(szt size, const T& initVal = T())
37  {
38  SpinLock::Scoped _(_headLock);
39  SpinLock::Scoped __(_tailLock);
40  setCapacity(size);
41  //Init new data
42  sdt dif = _capacity - _size;
43  for (sdt i = 0; i < dif; ++i) _alloc.construct(_data + ringIndex(_head+_size+i), initVal);
44  _size = size;
45  _tail = _head;
46  }
47 
49  template<class T_>
50  void push_front(T_&& val)
51  {
52  //At size == 0, head and tail are vying to push the same first spot
53  //At size == capacity-1, head and tail are vying to push the same last spot
54  //At size == capacity, expansion is needed
55  SpinLock::Scoped _(_headLock);
56  SpinLock::Scoped __(_tailLock, _size == 0 || _size >= _capacity-1 ? lock::Op::lock : lock::Op::defer);
57  if (_size == _capacity) expand();
58  _head = ringDec(_head);
59  _alloc.construct(_data + _head, forward<T_>(val));
60  ++_size;
61  }
62 
64  template<class T_>
65  void push_back(T_&& val)
66  {
67  SpinLock::Scoped headLock(_headLock, lock::Op::defer);
68  SpinLock::Scoped tailLock(_tailLock);
69  //Lock head first to prevent deadlock
70  if (_size == 0 || _size >= _capacity-1) { tailLock.unlock(); headLock.lock(); tailLock.lock(); }
71  if (_size == _capacity) expand();
72  _alloc.construct(_data + _tail, forward<T_>(val));
73  _tail = ringInc(_tail);
74  ++_size;
75  }
76 
79  {
80  //At size == 1, head and tail are vying to pop the last spot
81  SpinLock::Scoped _(_headLock);
82  SpinLock::Scoped __(_tailLock, _size == 1 ? lock::Op::lock : lock::Op::defer);
83  if (_size == 0) return false;
84  if (val) val = move(_data[_head]);
85  _alloc.destroy(_data + _head);
86  _head = ringInc(_head);
87  --_size;
88  return true;
89  }
90 
93  {
94  SpinLock::Scoped headLock(_headLock, lock::Op::defer);
95  SpinLock::Scoped tailLock(_tailLock);
96  if (_size == 1) { tailLock.unlock(); headLock.lock(); tailLock.lock(); }
97  if (_size == 0) return false;
98  _tail = ringDec(_tail);
99  if (val) val = move(_data[_tail]);
100  _alloc.destroy(_data + _tail);
101  --_size;
102  return true;
103  }
104 
106  void clear() { while (pop_back()); }
107 
109  szt size() const { return _size; }
111  bool empty() const { return size() == 0; }
112 
113 private:
114  szt ringIndex(szt index) const { return index % _capacity; }
115  szt ringInc(szt index) const { return index >= _capacity - 1 ? 0 : index + 1; }
116  szt ringDec(szt index) const { return index == 0 ? _capacity - 1 : index - 1; }
117 
118  void setCapacity(szt capacity)
119  {
120  if (capacity == _capacity) return;
121  //Get size (active element count) of new array, may be smaller than old
122  szt size = capacity < _size ? capacity : _size.load();
123  //Alloc new array
124  T* data = nullptr;
125  if (capacity > 0)
126  {
127  data = _alloc.allocate(capacity);
128  //Copy active elements to new array (new head is at 0)
129  if (_size > 0)
130  {
131  szt copyTail = ringIndex(_head + size);
132  if (copyTail > _head)
133  //Contiguous region
134  std::copy(_data.get() + _head, _data + copyTail, data);
135  else
136  {
137  //Split region, loops around end
138  std::copy(_data.get() + _head, _data + _capacity, data);
139  std::copy(_data.get(), _data + copyTail, data + (_capacity - _head));
140  }
141  }
142  }
143  //Destroy any active elements that don't fit into new array
144  sdt dif = _size - size;
145  for (sdt i = 0; i < dif; ++i) _alloc.destroy(_data + ringIndex(_head+size+i));
146  //Set new array
147  _data.set(data);
148  _capacity = capacity;
149  _size = size;
150  _head = 0;
151  _tail = size;
152  }
153 
154  void expand() { setCapacity(_capacity + _capacity/2 + 1); } //Expand 50%
155 
156  Alloc _alloc;
157  UniquePtr<T, finalize<T,Alloc>> _data;
158  szt _capacity;
159  Atomic<szt> _size;
160  szt _head;
161  szt _tail;
162  SpinLock _headLock;
163  SpinLock _tailLock;
164 };
165 
166 } }
Lock (blocking)
szt size() const
Number of elements in deque.
Definition: SpscDeque.h:109
Alloc_::template rebind< T >::other Alloc
Definition: SpscDeque.h:20
void push_back(T_ &&val)
Add new element constructed with val onto the end of the list.
Definition: SpscDeque.h:65
static optnull_t optnull
Null optional, use to reset an optional to an uninitialized state or test for initialization.
Definition: Optional.h:12
void resize(szt size, const T &initVal=T())
Resize the deque to contain a number of elements.
Definition: SpscDeque.h:36
ptrdiff_t sdt
Size difference type, shorthand for ptrdiff_t.
Definition: Core.h:92
Inherit to declare that class is not copyable.
Definition: Meta.h:286
bool pop_back(optional< T & > val=optnull)
Pop element from the end of the list and move it into val. Returns true on success, false if there is no element to pop.
Definition: SpscDeque.h:92
void clear()
Remove all elements.
Definition: SpscDeque.h:106
static auto _
Definition: Module.cpp:8
~SpscDeque()
Definition: SpscDeque.h:33
void push_front(T_ &&val)
Insert new element constructed with val at the beginning of the list.
Definition: SpscDeque.h:50
void unlock()
Definition: Unique.h:103
Not yet locked, will lock manually.
void lock()
Definition: Unique.h:95
size_t szt
Size type, shorthand for size_t.
Definition: Core.h:90
T * alloc(szt count=1)
Allocate memory for count number of T objects. Objects are not constructed.
Definition: Allocator.h:31
Deque that is lock-free only when used by a single producer and consumer, otherwise contention is spl...
Definition: SpscDeque.h:16
Functor to delete a pointer.
Definition: Allocator.h:161
SpscDeque(szt size=0, const T &initVal=T(), const Alloc &alloc=Alloc())
Definition: SpscDeque.h:22
Specialization for references.
Definition: Optional.h:132
bool empty() const
Check whether the deque does not contain any elements.
Definition: SpscDeque.h:111
T value_type
Definition: SpscDeque.h:19
bool pop_front(optional< T & > val=optnull)
Pop element from the beginning of the list and move it into val. Returns true on success, false if there is no element to pop.
Definition: SpscDeque.h:78
Global Honeycomb namespace.