multithreading
 All Classes Functions Variables Pages
nonblocking_call_queue.h
1 /*
2  ==============================================================================
3 
4  audiothread_fifo.h
5  Created: 20 Jan 2014 11:34:38am
6  Author: Jim Credland
7 
8  ==============================================================================
9 */
10 
11 #ifndef AUDIOTHREAD_FIFO_H_INCLUDED
12 #define AUDIOTHREAD_FIFO_H_INCLUDED
13 
14 
38 {
39 public:
40  LockFreeCallQueue (int RingBufferSize)
41  :
42  fifo (RingBufferSize),
43  acceptingJobs (true)
44  {
45  // TODO: data should be aligned to a cache line boundary.
46  // Allocate double size buffer to easily support variable length messages,
47  // by hanging them over the end of the buffer.
48  fifodata = new char[ RingBufferSize * 2 ];
49  }
50 
52  {
53  delete [] fifodata;
54  }
55 
57  bool isEmpty()
58  {
59  return fifo.getTotalSize() == fifo.getFreeSpace();
60  }
61 
72  {
73  return fifo.getFreeSpace();
74  }
75 
98  template <class Functor>
99  bool callf (Functor const& f)
100  {
101  if (! acceptingJobs)
102  return false;
103 
104  /* allocSize cannot be bigger than 2Gb ok!. */
105  // jassert (sizeof (WorkItem<Functor>) < std::numeric_limits<int>::max());
106 
107  const int allocSize = static_cast<int>(roundUpToCacheLineBoundary (sizeof (WorkItem<Functor>)));
108  int idx1, idx2, sz1, sz2;
109  fifo.prepareToWrite (allocSize, idx1, sz1, idx2, sz2);
110 
111  if (sz1 + sz2 < allocSize)
112  return false;
113 
114  /* double size buffer means we can ignore idx2 and sz2. Weird syntax for new is a 'placement
115  * new'. Familar only if you write custom allocators regularly. */
116  new (fifodata + idx1) WorkItem<Functor> (f);
117  fifo.finishedWrite (allocSize);
118 
119  return true;
120  }
121 
130  bool synchronize()
131  {
132  bool didSomething = false;
133 
134  while (fifo.getNumReady() > 0)
135  {
136  int idx1, idx2, sz1, sz2;
137  fifo.prepareToRead (1, idx1, sz1, idx2, sz2);
138  didSomething = true;
139  Work* w = reinterpret_cast<Work*> (fifodata + idx1);
140  /* notice only one function pointer invocation here, not two virtual function calls. */
141  const int sizeofWorkItem = (*w->execAndDestructFn) (w);
142  const int allocSize = roundUpToCacheLineBoundary (sizeofWorkItem);
143  fifo.finishedRead (allocSize);
144  }
145 
146  return didSomething;
147  }
148 
155  void stop()
156  {
157  acceptingJobs = false;
158  }
159 private:
160  /* Avoid a vtable and two separate virtual function dispatches (operator() and destructor) by putting
161  everything into a single function and implementing our own virtual function call. */
162 
167  typedef int (*WorkExecAndDestructFunctionPtr) (void* workItemStorage);
168 
169  class Work
170  {
171  // NOTE: no vtable.
172  public:
173  Work (WorkExecAndDestructFunctionPtr f) :
174  execAndDestructFn (f)
175  { }
176  WorkExecAndDestructFunctionPtr execAndDestructFn;
177  };
178 
180  template <class Functor>
181  struct WorkItem : public Work
182  {
183  explicit WorkItem (Functor const& fun) :
184  Work (&WorkItem::myExecAndDestruct),
185  myCall (fun)
186  {}
187 
188  private:
189  static int myExecAndDestruct (void* workItemStorage)
190  {
191  /* cast to *concrete* work item pointer of this template type */
192  WorkItem* that = reinterpret_cast<WorkItem*> (workItemStorage);
193  that->myCall();
194  that->~WorkItem(); // invoke concrete dtor (destructs functor)
195  return static_cast<int>(sizeof (WorkItem));
196  }
197 
198  Functor myCall;
199  };
200 
201  /* For clarity (maybe) what's happening here is:
202  1 There are instances of the templated class WorkItem for each type of Functor, and hence
203  multiple versions of myExecAndDestruct.
204  2 When the WorkItem is created the Work::execAndDestructFn pointer is set to
205  point to the appropriate instance of myExecAndDestruct - the pointer is passed
206  to the Work superclass constructor by the WorkItem<Functor> class.
207 
208  To call we:
209  - Get the data from the fifo.
210  - Cast it to be a Work.
211  - Call the appropriate instance of myExecAndDestruct
212  via the execAndDestructFn pointer.
213  */
214 
215  AbstractFifo fifo;
216  char* fifodata;
217 
218  /* Used to avoid false sharing and to give correct alignment to embedded structs. */
219  int roundUpToCacheLineBoundary (int x)
220  {
221  return x; // TODO
222  }
223 
224  bool acceptingJobs;
225 };
226 
227 
228 
229 
230 
231 #endif // AUDIOTHREAD_FIFO_H_INCLUDED
bool synchronize()
Execute all the calls in the queue.
Definition: nonblocking_call_queue.h:130
int getFreeSpace()
Return the amount of free space in the queue.
Definition: nonblocking_call_queue.h:71
Allows a function call to be executed on a different thread, in a fast lock-safe and thread-safe mann...
Definition: nonblocking_call_queue.h:37
bool callf(Functor const &f)
Calls a function, via the queue, on a different thread.
Definition: nonblocking_call_queue.h:99
void stop()
Definition: nonblocking_call_queue.h:155
bool isEmpty()
return true if the queue is empty.
Definition: nonblocking_call_queue.h:57