c++-gtk-utils
async_channel.h
Go to the documentation of this file.
1 /* Copyright (C) 2016 Chris Vine
2 
3 The library comprised in this file or of which this file is part is
4 distributed by Chris Vine under the GNU Lesser General Public
5 License as follows:
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Lesser General Public License
9  as published by the Free Software Foundation; either version 2.1 of
10  the License, or (at your option) any later version.
11 
12  This library is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License, version 2.1, for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License, version 2.1, along with this library (see the file LGPL.TXT
19  which came with this source code package in the c++-gtk-utils
20  sub-directory); if not, write to the Free Software Foundation, Inc.,
21  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 
23 However, it is not intended that the object code of a program whose
24 source code instantiates a template from this file or uses macros or
25 inline functions (of any length) should by reason only of that
26 instantiation or use be subject to the restrictions of use in the GNU
27 Lesser General Public License. With that in mind, the words "and
28 macros, inline functions and instantiations of templates (of any
29 length)" shall be treated as substituted for the words "and small
30 macros and small inline functions (ten lines or less in length)" in
31 the fourth paragraph of section 5 of that licence. This does not
32 affect any other reason why object code may be subject to the
33 restrictions in that licence (nor for the avoidance of doubt does it
34 affect the application of section 2 of that licence to modifications
35 of the source code in this file).
36 
37 */
38 
39 /**
40  * @file async_channel.h
41  * @brief This file provides a "channel" class for inter-thread communication.
42  *
43  * AsyncChannel is similar to the AsyncQueueDispatch class, in that it
44  * provides a means of sending data between threads. Producer threads
45  * push data onto the queue and consumer threads pop them off in a
46  * thread safe way, and if there are no data in the channel any
47  * consumer thread will block until a producer thread pushes an item
48  * onto it. However, unlike the AsyncQueueDispatch class, it has a
49  * fixed maximum size as part of its type, which may be any size
50  * greater than 0, and if the number of data items still in the
51  * channel is such as to make the channel full, then producer threads
52  * will block on the channel until a consumer thread pops an item from
53  * it.
54  *
55  * It therefore provides for back pressure on producer threads which
56  * will automatically prevent the channel being overwhelmed by
57  * producer threads pushing more items onto the queue than consumer
58  * threads have the capacity to take off it.
59  *
60  * AsyncChannel is useful where this feature is important, and an
61  * AsyncChannel object can be used with any number of producer threads
62  * and consumer threads. However, under heavy contention with complex
63  * data item types AsyncQueueDispatch objects will usually be faster.
64  * Under lower contention and with simpler data types (or where
65  * temporary objects with non-complex move constructors are pushed),
66  * an AsyncChannel object may be faster as it can benefit from its
67  * fixed buffer size (the AsyncChannel implementation uses a circular
68  * buffer with in-buffer construction of data items).
69  *
70  * A channel with a size of 1 is often referred to as an unbuffered
71  * channel (it takes only one data item and once a datum has been
72  * placed in the channel, a producer will block until it has been
73  * popped). Channels with a size greater than 1 are often referred to
74  * as buffered channels.
75  *
76  * This class is available from version 2.0.31.
77  */
78 
79 #ifndef CGU_ASYNC_CHANNEL_H
80 #define CGU_ASYNC_CHANNEL_H
81 
82 #include <utility> // for std::move and std::forward
83 #include <new> // for std::bad_alloc
84 #include <cstddef> // for std::size_t
85 #include <cstdlib> // for std::malloc
86 
87 #include <pthread.h>
88 
89 #include <c++-gtk-utils/mutex.h>
90 #include <c++-gtk-utils/thread.h>
92 
93 #ifdef CGU_USE_SCHED_YIELD
94 #include <sched.h>
95 #else
96 #include <unistd.h>
97 #endif
98 
99 namespace Cgu {
100 
101 // function for pthread_cleanup_push() in AsyncChannel::push() and
102 // AsyncChannel::pop() methods
103 extern "C" {
104 inline void cgu_async_channel_waiters_dec(void* arg) {
105  --(*static_cast<std::size_t*>(arg));
106 }
107 } // extern "C"
108 
109 /**
110  * @class AsyncChannel async_channel.h c++-gtk-utils/async_channel.h
111  * @brief A thread-safe "channel" class for inter-thread communication.
112  * @sa AsyncQueue AsyncQueueDispatch AsyncChannel AsyncResult
113  *
114  * AsyncChannel is similar to the AsyncQueueDispatch class, in that it
115  * provides a means of sending data between threads. Producer threads
116  * push data onto the queue and consumer threads pop them off in a
117  * thread safe way, and if there are no data in the channel any
118  * consumer thread will block until a producer thread pushes an item
119  * onto it. However, unlike the AsyncQueueDispatch class, it has a
120  * fixed maximum size as part of its type, which may be any size
121  * greater than 0, and if the number of data items still in the
122  * channel is such as to make the channel full, then producer threads
123  * will block on the channel until a consumer thread pops an item from
124  * it.
125  *
126  * It therefore provides for back pressure on producer threads which
127  * will automatically prevent the channel being overwhelmed by
128  * producer threads pushing more items onto the queue than consumer
129  * threads have the capacity to take off it.
130  *
131  * AsyncChannel is useful where this feature is important, and an
132  * AsyncChannel object can be used with any number of producer threads
133  * and consumer threads. However, under heavy contention with complex
134  * data item types AsyncQueueDispatch objects will usually be faster.
135  * Under lower contention and with simpler data types (or where
136  * temporary objects with non-complex move constructors are pushed),
137  * an AsyncChannel object may be faster as it can benefit from its
138  * fixed buffer size (the AsyncChannel implementation uses a circular
139  * buffer with in-buffer construction of data items).
140  *
141  * AsyncChannel objects are instantiated with firstly a template type
142  * 'T' and secondly a template integer value 'n'. 'T' is the type of
143  * the data items to be placed on the queue. 'n' is the size of the
144  * channel, which as mentioned may be any size greater than 0.
145  *
146  * A channel with a size of 1 is often referred to as an unbuffered
147  * channel (it takes only one data item and once a datum has been
148  * placed in the channel, a producer will block until it has been
149  * popped). Channels with a size greater than 1 are often referred to
150  * as buffered channels.
151  *
152  * This class is available from version 2.0.31.
153  */
154 
155 /*
156  * We have to use Thread::Cond::broadcast() and not
157  * Thread::Cond::signal(), because it is possible to have at any one
158  * time both a producer and a consumer waiting on the AsyncChannel's
159  * condition variable. This can create a "thundering herd" problem if
160  * there are a large number of threads waiting on the channel, but it
161  * is within the range of the acceptable. As an example of the issue
162  * requiring this approach, take this case:
163  *
164  * Let there be a channel which has a capacity of one. Let the
165  * channel already have an item in it from some past producer. In
166  * addition, let two producer threads be currently in a cond-wait,
167  * waiting for the channel to become empty in order to put an item in
168  * it.
169  *
170  * A first consumer thread starts to remove the item in the channel.
171  * It acquires the channel's mutex without blocking because it is not
172  * locked. It sees there is an item in the channel so does not begin
173  * a cond-wait. Meanwhile, immediately after the first consumer
174  * thread acquires the mutex a second consumer thread tries to obtain
175  * an item from the channel and thus will block when trying to acquire
176  * the locked mutex.
177  *
178  * The first consumer thread then removes the item from the channel's
179  * queue and signals the cond-var - which causes one of the producer
180  * threads to wake-up and block on trying to acquire the mutex ("the
181  * first producer"). So for the producers, we now have the first
182  * producer contending on the mutex after being so awoken and the
183  * second producer still waiting on the cond-var. And we have two
184  * threads now contending on the mutex - the thread of the second
185  * consumer and the thread of the first producer. One of these
186  * threads will acquire the mutex when the first consumer releases it
187  * after signalling the cond-var, and it is unspecified which one.
188  *
189  * If it is the second consumer, it will find the channel empty and
190  * thus enter a cond-wait (so we now have the second producer and the
191  * second consumer waiting on the same cond-var) and release the
192  * mutex. This will cause the first producer to acquire the mutex,
193  * which will then add the item to the channel, signal the cond-var
194  * (which will cause either the second producer or second consumer to
195  * awaken), and release the mutex. If it is the second producer which
196  * awakens, its thread will acquire the mutex, find the channel full,
197  * enter a cond-wait again and release the mutex. Now we are stuffed
198  * because there is nothing to awaken the remaining consumer even
199  * though there is something in the channel.
200  */
201 template <class T, std::size_t n> class AsyncChannel {
202 public:
203  typedef T value_type;
204  typedef std::size_t size_type;
205 private:
206  mutable Thread::Mutex mutex;
207  Thread::Cond cond;
208  size_type size; // number of available items in channel
209  size_type idx; // index of first available item in channel (this
210  // value is meaningless when size == 0)
211  size_type waiters;
212  enum Status {normal, closed, destructing} status;
213  // TODO: when this library moves to a minimum requirement of
214  // gcc-4.8, use an object-resident char array for 'buf' with
215  // alignas<T>, to improve locality
216  T* buf;
217 
218 public:
219 /**
220  * This class cannot be copied. The copy constructor is deleted.
221  */
222  AsyncChannel(const AsyncChannel&) = delete;
223 
224 /**
225  * This class cannot be copied. The assignment operator is deleted.
226  */
227  AsyncChannel& operator=(const AsyncChannel&) = delete;
228 
229 /**
230  * Closes the channel. This means that (i) any threads blocking on a
231  * full channel with a call to push() or emplace() will unblock with a
232  * false return value, and any calls to those methods after the
233  * closure will return immediately with a false return value, (ii) any
234  * threads blocking on an empty channel with calls to pop() or
235  * move_pop() will unblock with a false return value, (iii) any data
236  * items remaining in the channel which were pushed to the channel
237  * prior to the closure of the channel can be popped after that
238  * closure by further calls to pop() or move_pop(), which will return
239  * normally with a true return value, and (iv) after any such
240  * remaining data items have been removed, any subsequent calls to
241  * pop() or move_pop() will return with a false return value.
242  *
243  * If called more than once, this method will do nothing.
244  *
245  * This method will not throw. It is thread safe - any thread may
246  * call it.
247  *
248  * One of the main purposes of this method is to enable a producer
249  * thread to inform a consumer thread that nothing more will be put in
250  * the channel by it for the consumer: for such cases, as mentioned
251  * once everything pushed to the channel prior to its closure has been
252  * extracted from the channel by pop() or move_pop() calls, any
253  * further pop() or move_pop() calls will return false. At that point
254  * the consumer thread can abandon and destroy the AsyncChannel
255  * object.
256  *
257  * Since 2.0.31
258  */
259  void close() {
260  Thread::Mutex::Lock lock{mutex};
261  if (status == normal) {
262  status = closed;
263  cond.broadcast();
264  }
265  }
266 
267 /**
268  * Pushes an item onto the channel. This method will only throw if the
269  * copy constructor of the pushed item throws, and has strong
270  * exception safety in such a case. It is thread safe.
271  *
272  * If the number of items already in the channel is equal to the size
273  * of the channel, then this method blocks until either room becomes
274  * available for the item by virtue of another thread calling the
275  * pop() or move_pop() methods, or another thread calls the close()
276  * method. If it blocks, the wait comprises a cancellation
277  * point. This method is cancellation safe if the stack unwinds on
278  * cancellation, as cancellation is blocked while the channel is being
279  * operated on after coming out of a wait.
280  *
281  * @param obj The item to be pushed onto the channel.
282  * @return If the push succeeds (whether after blocking or not
283  * blocking) this method returns true. If this method unblocks
284  * because the channel has been closed, or any subsequent calls to
285  * this method are made after the channel has been closed, this method
286  * returns false.
287  *
288  * Since 2.0.31
289  */
290  bool push(const value_type& obj) {
291  bool waiting = false;
292  Thread::Mutex::Lock lock{mutex};
293  if (status != normal) return false;
294 
295  // the only function call that could be a cancellation point
296  // within this pthread_cleanup_push/pop block is the call to
297  // Cond::wait() - if there is a cancellation request while
298  // blocking in that call we need to decrement the waiters count
299  // with a cancellation handler. The push is efficient enough not
300  // to have to unlock the mutex - the cleanup_push/pop block
301  // enables the cleanup macros to construct all the cleanup datum
302  // on the function stack set up at function entry
303  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
304  while (size >= n && status == normal) {
305  if (!waiting) {
306  ++waiters;
307  waiting = true;
308  }
309  cond.wait(mutex); // cancellation point
310  }
312  pthread_cleanup_pop(false);
313  // we need to keep a local copy of 'status' because as soon as
314  // 'waiters' is decremented the AsyncChannel object could be
315  // destroyed if status == destructing
316  Status local_status = status;
317  if (waiting) --waiters;
318  if (local_status != normal) return false;
319  // next is the index of the next available space in the channel
320  size_type next = (idx + size) % n;
321  new (static_cast<void*>(buf + next)) T{obj};
322  ++size;
323  cond.broadcast();
324  return true;
325  }
326 
327 /**
328  * Pushes an item onto the channel. This method will only throw if the
329  * move constructor, or if none the copy constructor, of the pushed
330  * item throws, and has strong exception safety in such a case. It is
331  * thread safe.
332  *
333  * If the number of items already in the channel is equal to the size
334  * of the channel, then this method blocks until either room becomes
335  * available for the item by virtue of another thread calling the
336  * pop() or move_pop() methods, or another thread calls the close()
337  * method. If it blocks, the wait comprises a cancellation
338  * point. This method is cancellation safe if the stack unwinds on
339  * cancellation, as cancellation is blocked while the channel is being
340  * operated on after coming out of a wait.
341  *
342  * @param obj The item to be pushed onto the channel.
343  * @return If the push succeeds (whether after blocking or not
344  * blocking) this method returns true. If this method unblocks
345  * because the channel has been closed, or any subsequent calls to
346  * this method are made after the channel has been closed, this method
347  * returns false.
348  *
349  * Since 2.0.31
350  */
351  bool push(value_type&& obj) {
352  bool waiting = false;
353  Thread::Mutex::Lock lock{mutex};
354  if (status != normal) return false;
355 
356  // the only function call that could be a cancellation point
357  // within this pthread_cleanup_push/pop block is the call to
358  // Cond::wait() - if there is a cancellation request while
359  // blocking in that call we need to decrement the waiters count
360  // with a cancellation handler. The push is efficient enough not
361  // to have to unlock the mutex - the cleanup_push/pop block
362  // enables the cleanup macros to construct all the cleanup datum
363  // on the function stack set up at function entry
364  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
365  while (size >= n && status == normal) {
366  if (!waiting) {
367  ++waiters;
368  waiting = true;
369  }
370  cond.wait(mutex); // cancellation point
371  }
373  pthread_cleanup_pop(false);
374  // we need to keep a local copy of 'status' because as soon as
375  // 'waiters' is decremented the AsyncChannel object could be
376  // destroyed if status == destructing
377  Status local_status = status;
378  if (waiting) --waiters;
379  if (local_status != normal) return false;
380  // next is the index of the next available space in the channel
381  size_type next = (idx + size) % n;
382  new (static_cast<void*>(buf + next)) T{std::move(obj)};
383  ++size;
384  cond.broadcast();
385  return true;
386  }
387 
388 /**
389  * Pushes an item onto the channel by constructing it in place from
390  * the given arguments. This method will only throw if the constructor
391  * of the emplaced item throws, and has strong exception safety in
392  * such a case. It is thread safe.
393  *
394  * If the number of items already in the channel is equal to the size
395  * of the channel, then this method blocks until either room becomes
396  * available for the item by virtue of another thread calling the
397  * pop() or move_pop() methods, or another thread calls the close()
398  * method. If it blocks, the wait comprises a cancellation
399  * point. This method is cancellation safe if the stack unwinds on
400  * cancellation, as cancellation is blocked while the channel is being
401  * operated on after coming out of a wait.
402  *
403  * @param args The constructor arguments for the item to be pushed
404  * onto the channel.
405  * @return If the push succeeds (whether after blocking or not
406  * blocking) this method returns true. If this method unblocks
407  * because the channel has been closed, or any subsequent calls to
408  * this method are made after the channel has been closed, this method
409  * returns false.
410  *
411  * Since 2.0.31
412  */
413  template<class... Args>
414  bool emplace(Args&&... args) {
415  bool waiting = false;
416  Thread::Mutex::Lock lock{mutex};
417  if (status != normal) return false;
418 
419  // the only function call that could be a cancellation point
420  // within this pthread_cleanup_push/pop block is the call to
421  // Cond::wait() - if there is a cancellation request while
422  // blocking in that call we need to decrement the waiters count
423  // with a cancellation handler. The push is efficient enough not
424  // to have to unlock the mutex - the cleanup_push/pop block
425  // enables the cleanup macros to construct all the cleanup datum
426  // on the function stack set up at function entry
427  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
428  while (size >= n && status == normal) {
429  if (!waiting) {
430  ++waiters;
431  waiting = true;
432  }
433  cond.wait(mutex); // cancellation point
434  }
436  pthread_cleanup_pop(false);
437  // we need to keep a local copy of 'status' because as soon as
438  // 'waiters' is decremented the AsyncChannel object could be
439  // destroyed if status == destructing
440  Status local_status = status;
441  if (waiting) --waiters;
442  if (local_status != normal) return false;
443  // next is the index of the next available space in the channel
444  size_type next = (idx + size) % n;
445  new (static_cast<void*>(buf + next)) T{std::forward<Args>(args)...};
446  ++size;
447  cond.broadcast();
448  return true;
449  }
450 
451 /**
452  * Pops an item from the channel using the item type's copy assignment
453  * operator. This method will only throw if that operator throws or
454  * the contained item's destructor throws. It has strong exception
455  * safety, provided the destructor of the contained item does not
456  * throw. See also the move_pop() method. It is thread safe.
457  *
458  * If the channel is empty, then this method blocks until either an
459  * item becomes available by virtue of another thread calling the
460  * emplace() or push() methods, or another thread calls the close()
461  * method. If it blocks, the wait comprises a cancellation
462  * point. This method is cancellation safe if the stack unwinds on
463  * cancellation, as cancellation is blocked while the channel is being
464  * operated on after coming out of a wait.
465  *
466  * @param obj A value type reference to which the item at the front of
467  * the channel will be copy assigned.
468  * @return If the pop succeeds (whether after blocking or not
469  * blocking) this method returns true. If this method unblocks
470  * because the channel has been closed or any subsequent calls to this
471  * method are made, and there are no remaining items in the channel,
472  * this method returns false.
473  *
474  * Since 2.0.31
475  */
476  bool pop(value_type& obj) {
477  bool waiting = false;
478  Thread::Mutex::Lock lock{mutex};
479 
480  // the only function call that could be a cancellation point
481  // within this pthread_cleanup_push/pop block is the call to
482  // Cond::wait() - if there is a cancellation request while
483  // blocking in that call we need to decrement the waiters count
484  // with a cancellation handler. The push is efficient enough not
485  // to have to unlock the mutex - the cleanup_push/pop block
486  // enables the cleanup macros to construct all the cleanup datum
487  // on the function stack set up at function entry
488  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
489  while (!size && status == normal) {
490  if (!waiting) {
491  ++waiters;
492  waiting = true;
493  }
494  cond.wait(mutex);
495  }
497  pthread_cleanup_pop(false);
498 
499  if (status == destructing) {
500  // decrementing waiters must be the last thing we do as it might
501  // cause the destructor to return
502  if (waiting) --waiters;
503  return false;
504  }
505  else if (size) { // status == normal, or status == closed && size > 0
506  size_type old_idx = idx;
507  obj = buf[old_idx];
508  ++idx;
509  if (idx == n) idx = 0;
510  --size;
511  try {
512  buf[old_idx].~T();
513  }
514  catch (...) {
515  // we might as well keep the AsyncChannel object's waiters in
516  // a workable state even if the item's destructor throws
517  if (waiting) --waiters;
518  cond.broadcast();
519  throw;
520  }
521  if (waiting) --waiters;
522  cond.broadcast();
523  return true;
524  }
525  else { // status == closed and size == 0
526  if (waiting) --waiters;
527  return false;
528  }
529  }
530 
531 /**
532  * Pops an item from the channel using the item type's move assignment
533  * operator if it has one, or if not its copy assignment operator
534  * (this method is identical to the pop() method if that type has no
535  * move assignment operator). This method will only throw if that
536  * operator throws or the contained item's destructor throws. It has
537  * strong exception safety, provided the destructor of the contained
538  * item does not throw and the move assignment operator of the
539  * contained item has strong exception safety. Use this method in
540  * preference to the pop() method if it is known that the contained
541  * items' move assignment operator does not throw or is strongly
542  * exception safe, or if the use case does not require strong
543  * exception safety. This method must be used in place of the pop()
544  * method if the contained item has a move assignment operator but no
545  * copy assignment operator (such as a std::unique_ptr object). It is
546  * thread safe.
547  *
548  * If the channel is empty, then this method blocks until either an
549  * item becomes available by virtue of another thread calling the
550  * emplace() or push() methods, or another thread calls the close()
551  * method. If it blocks, the wait comprises a cancellation
552  * point. This method is cancellation safe if the stack unwinds on
553  * cancellation, as cancellation is blocked while the channel is being
554  * operated on after coming out of a wait.
555  *
556  * @param obj A value type reference to which the item at the front of
557  * the channel will be move assigned.
558  * @return If the pop succeeds (whether after blocking or not
559  * blocking) this method returns true. If this method unblocks
560  * because the channel has been closed or any subsequent calls to this
561  * method are made, and there are no remaining items in the channel,
562  * this method returns false.
563  *
564  * Since 2.0.31
565  */
566  bool move_pop(value_type& obj) {
567  bool waiting = false;
568  Thread::Mutex::Lock lock{mutex};
569 
570  // the only function call that could be a cancellation point
571  // within this pthread_cleanup_push/pop block is the call to
572  // Cond::wait() - if there is a cancellation request while
573  // blocking in that call we need to decrement the waiters count
574  // with a cancellation handler. The push is efficient enough not
575  // to have to unlock the mutex - the cleanup_push/pop block
576  // enables the cleanup macros to construct all the cleanup datum
577  // on the function stack set up at function entry
578  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
579  while (!size && status == normal) {
580  if (!waiting) {
581  ++waiters;
582  waiting = true;
583  }
584  cond.wait(mutex);
585  }
587  pthread_cleanup_pop(false);
588 
589  if (status == destructing) {
590  // decrementing waiters must be the last thing we do as it might
591  // cause the destructor to return
592  if (waiting) --waiters;
593  return false;
594  }
595  else if (size) { // status == normal, or status == closed && size > 0
596  size_type old_idx = idx;
597  obj = std::move(buf[old_idx]);
598  ++idx;
599  if (idx == n) idx = 0;
600  --size;
601  try {
602  buf[old_idx].~T();
603  }
604  catch (...) {
605  // we might as well keep the AsyncChannel object's waiters in
606  // a workable state even if the item's destructor throws
607  if (waiting) --waiters;
608  cond.broadcast();
609  throw;
610  }
611  if (waiting) --waiters;
612  cond.broadcast();
613  return true;
614  }
615  else { // status == closed and size == 0
616  if (waiting) --waiters;
617  return false;
618  }
619  }
620 
621 /**
622  * AsyncChannel objects are instantiated with firstly a template type
623  * 'T' and secondly a template integer value 'n'. 'T' is the type of
624  * the data items to be placed on the queue. 'n' is the size of the
625  * channel, which must be greater than 0. However, a circular buffer
626  * for that size will be allocated at construction time by this
627  * default constructor, so the given size should not be unnecessarily
628  * large. Where a large AsyncChannel object would be required,
629  * consider using AsyncQueueDispatch instead, which sizes itself
630  * dynamically.
631  *
632  * @exception std::bad_alloc The default constructor might throw this
633  * exception if memory is exhausted and the system throws in that
634  * case.
635  * @exception Thread::MutexError The default constructor might throw
636  * this exception if initialisation of the contained mutex fails. (It
637  * is often not worth checking for this, as it means either memory is
638  * exhausted or pthread has run out of other resources to create new
639  * mutexes.)
640  * @exception Thread::CondError The default constructor might throw
641  * this exception if initialisation of the contained condition
642  * variable fails. (It is often not worth checking for this, as it
643  * means either memory is exhausted or pthread has run out of other
644  * resources to create new condition variables.)
645  *
646  * Since 2.0.31
647  */
648  AsyncChannel(): size(0), idx(0), waiters(0), status(normal),
649  // locality would be better if we could use an
650  // object-resident char array for 'buf' with
651  // alignas(T), instead of allocating on the heap
652  // with malloc, but this is not supported by gcc
653  // until gcc-4.8, and we support gcc-4.6 onwards.
654  // Sometimes unions are used in pre-C++11 code to
655  // get round this, but this is only guaranteed to
656  // work where T is a POD (C++98/03) or has standard
657  // layout (C++11/14). Bummer.
658  buf(static_cast<T*>(std::malloc(sizeof(T) * n))) {
659  static_assert(n != 0, "AsyncChannel objects may not be created with size 0");
660  if (!buf) throw std::bad_alloc();
661  }
662 
663  /**
664  * The destructor does not throw unless the destructor of a data item
665  * in the channel throws. It is thread safe (any thread may delete
666  * the AsyncChannel object).
667  *
668  * It is not an error for a thread to destroy the AsyncChannel object
669  * and so invoke this destructor while another thread is blocking on
670  * it: instead the destructor will release any blocking threads. The
671  * destructor will not return until all threads (if any) blocking on
672  * the AsyncChannel object have been released.
673  *
674  * Since 2.0.31
675  */
677  mutex.lock();
678  status = destructing;
679  mutex.unlock();
680  cond.broadcast();
681 
682  // since all a waiting thread does upon status == destructing is
683  // to unblock and return, it is more efficient to spin gracefully
684  // until 'waiters' is 0 instead of starting another condition
685  // variable wait
686  for (;;) {
687  mutex.lock();
688  if (waiters) {
689  mutex.unlock();
690 #ifdef CGU_USE_SCHED_YIELD
691  sched_yield();
692 #else
693  usleep(10);
694 #endif
695  }
696  else {
697  mutex.unlock();
698  break;
699  }
700  }
701  while (size) {
702  buf[idx].~T();
703  ++idx;
704  if (idx == n) idx = 0;
705  --size;
706  }
707  std::free(buf);
708  }
709 
710 /* Only has effect if --with-glib-memory-slices-compat or
711  * --with-glib-memory-slices-no-compat option picked */
713 };
714 
715 #ifndef DOXYGEN_PARSING
716 
717 /* This is a specialization of AsyncChannel when instantiated with a
718  size of 1. This specialization allows a number of optimizations
719  for that case.
720 */
721 template <class T>
722 class AsyncChannel<T, 1> {
723 public:
724  typedef T value_type;
725  typedef std::size_t size_type;
726 private:
727  mutable Thread::Mutex mutex;
728  Thread::Cond cond;
729  size_type waiters;
730  bool full;
731  enum Status {normal, closed, destructing} status;
732  // TODO: when this library moves to a minimum requirement of
733  // gcc-4.8, use an object-resident char array for 'datum' with
734  // alignas<T>, to improve locality
735  T* datum;
736 
737 public:
738  AsyncChannel(const AsyncChannel&) = delete;
739 
740  AsyncChannel& operator=(const AsyncChannel&) = delete;
741 
742  void close() {
743  Thread::Mutex::Lock lock{mutex};
744  if (status == normal) {
745  status = closed;
746  cond.broadcast();
747  }
748  }
749 
750  bool push(const value_type& obj) {
751  bool waiting = false;
752  Thread::Mutex::Lock lock{mutex};
753  if (status != normal) return false;
754 
755  // the only function call that could be a cancellation point
756  // within this pthread_cleanup_push/pop block is the call to
757  // Cond::wait() - if there is a cancellation request while
758  // blocking in that call we need to decrement the waiters count
759  // with a cancellation handler. The push is efficient enough not
760  // to have to unlock the mutex - the cleanup_push/pop block
761  // enables the cleanup macros to construct all the cleanup datum
762  // on the function stack set up at function entry
763  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
764  while (full && status == normal) {
765  if (!waiting) {
766  ++waiters;
767  waiting = true;
768  }
769  cond.wait(mutex); // cancellation point
770  }
771  Thread::CancelBlock b;
772  pthread_cleanup_pop(false);
773  // we need to keep a local copy of 'status' because as soon as
774  // 'waiters' is decremented the AsyncChannel object could be
775  // destroyed if status == destructing
776  Status local_status = status;
777  if (waiting) --waiters;
778  if (local_status != normal) return false;
779  new (static_cast<void*>(datum)) T{obj};
780  full = true;
781  cond.broadcast();
782  return true;
783  }
784 
785  bool push(value_type&& obj) {
786  bool waiting = false;
787  Thread::Mutex::Lock lock{mutex};
788  if (status != normal) return false;
789 
790  // the only function call that could be a cancellation point
791  // within this pthread_cleanup_push/pop block is the call to
792  // Cond::wait() - if there is a cancellation request while
793  // blocking in that call we need to decrement the waiters count
794  // with a cancellation handler. The push is efficient enough not
795  // to have to unlock the mutex - the cleanup_push/pop block
796  // enables the cleanup macros to construct all the cleanup datum
797  // on the function stack set up at function entry
798  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
799  while (full && status == normal) {
800  if (!waiting) {
801  ++waiters;
802  waiting = true;
803  }
804  cond.wait(mutex); // cancellation point
805  }
806  Thread::CancelBlock b;
807  pthread_cleanup_pop(false);
808  // we need to keep a local copy of 'status' because as soon as
809  // 'waiters' is decremented the AsyncChannel object could be
810  // destroyed if status == destructing
811  Status local_status = status;
812  if (waiting) --waiters;
813  if (local_status != normal) return false;
814  new (static_cast<void*>(datum)) T{std::move(obj)};
815  full = true;
816  cond.broadcast();
817  return true;
818  }
819 
820  template<class... Args>
821  bool emplace(Args&&... args) {
822  bool waiting = false;
823  Thread::Mutex::Lock lock{mutex};
824  if (status != normal) return false;
825 
826  // the only function call that could be a cancellation point
827  // within this pthread_cleanup_push/pop block is the call to
828  // Cond::wait() - if there is a cancellation request while
829  // blocking in that call we need to decrement the waiters count
830  // with a cancellation handler. The push is efficient enough not
831  // to have to unlock the mutex - the cleanup_push/pop block
832  // enables the cleanup macros to construct all the cleanup datum
833  // on the function stack set up at function entry
834  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
835  while (full && status == normal) {
836  if (!waiting) {
837  ++waiters;
838  waiting = true;
839  }
840  cond.wait(mutex); // cancellation point
841  }
842  Thread::CancelBlock b;
843  pthread_cleanup_pop(false);
844  // we need to keep a local copy of 'status' because as soon as
845  // 'waiters' is decremented the AsyncChannel object could be
846  // destroyed if status == destructing
847  Status local_status = status;
848  if (waiting) --waiters;
849  if (local_status != normal) return false;
850  new (static_cast<void*>(datum)) T{std::forward<Args>(args)...};
851  full = true;
852  cond.broadcast();
853  return true;
854  }
855 
856  bool pop(value_type& obj) {
857  bool waiting = false;
858  Thread::Mutex::Lock lock{mutex};
859 
860  // the only function call that could be a cancellation point
861  // within this pthread_cleanup_push/pop block is the call to
862  // Cond::wait() - if there is a cancellation request while
863  // blocking in that call we need to decrement the waiters count
864  // with a cancellation handler. The push is efficient enough not
865  // to have to unlock the mutex - the cleanup_push/pop block
866  // enables the cleanup macros to construct all the cleanup datum
867  // on the function stack set up at function entry
868  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
869  while (!full && status == normal) {
870  if (!waiting) {
871  ++waiters;
872  waiting = true;
873  }
874  cond.wait(mutex);
875  }
876  Thread::CancelBlock b;
877  pthread_cleanup_pop(false);
878 
879  if (status == destructing) {
880  // decrementing waiters must be the last thing we do as it might
881  // cause the destructor to return
882  if (waiting) --waiters;
883  return false;
884  }
885  else if (full) { // status == normal, or status == closed && full
886  obj = *datum;
887  full = false;
888  try {
889  datum->~T();
890  }
891  catch (...) {
892  // we might as well keep the AsyncChannel object's waiters in
893  // a workable state even if the item's destructor throws
894  if (waiting) --waiters;
895  cond.broadcast();
896  throw;
897  }
898  if (waiting) --waiters;
899  cond.broadcast();
900  return true;
901  }
902  else { // status == closed and !full
903  if (waiting) --waiters;
904  return false;
905  }
906  }
907 
908  bool move_pop(value_type& obj) {
909  bool waiting = false;
910  Thread::Mutex::Lock lock{mutex};
911 
912  // the only function call that could be a cancellation point
913  // within this pthread_cleanup_push/pop block is the call to
914  // Cond::wait() - if there is a cancellation request while
915  // blocking in that call we need to decrement the waiters count
916  // with a cancellation handler. The push is efficient enough not
917  // to have to unlock the mutex - the cleanup_push/pop block
918  // enables the cleanup macros to construct all the cleanup datum
919  // on the function stack set up at function entry
920  pthread_cleanup_push(cgu_async_channel_waiters_dec, &this->waiters);
921  while (!full && status == normal) {
922  if (!waiting) {
923  ++waiters;
924  waiting = true;
925  }
926  cond.wait(mutex);
927  }
928  Thread::CancelBlock b;
929  pthread_cleanup_pop(false);
930 
931  if (status == destructing) {
932  // decrementing waiters must be the last thing we do as it might
933  // cause the destructor to return
934  if (waiting) --waiters;
935  return false;
936  }
937  else if (full) { // status == normal, or status == closed && full
938  obj = std::move(*datum);
939  full = false;
940  try {
941  datum->~T();
942  }
943  catch (...) {
944  // we might as well keep the AsyncChannel object's waiters in
945  // a workable state even if the item's destructor throws
946  if (waiting) --waiters;
947  cond.broadcast();
948  throw;
949  }
950  if (waiting) --waiters;
951  cond.broadcast();
952  return true;
953  }
954  else { // status == closed and !full
955  if (waiting) --waiters;
956  return false;
957  }
958  }
959 
960  AsyncChannel(): waiters(0), full(false), status(normal),
961  // locality would be better if we could use an
962  // object-resident char array for 'datum' with
963  // alignas(T), instead of allocating on the heap
964  // with malloc, but this is not supported by gcc
965  // until gcc-4.8, and we support gcc-4.6 onwards.
966  // Sometimes unions are used in pre-C++11 code to
967  // get round this, but this is only guaranteed to
968  // work where T is a POD (C++98/03) or has standard
969  // layout (C++11/14). Bummer.
970  datum(static_cast<T*>(std::malloc(sizeof(T)))) {
971  if (!datum) throw std::bad_alloc();
972  }
973 
974  ~AsyncChannel() {
975  mutex.lock();
976  status = destructing;
977  mutex.unlock();
978  cond.broadcast();
979 
980  // since all a waiting thread does upon status == destructing is
981  // to unblock and return, it is more efficient to spin gracefully
982  // until 'waiters' is 0 instead of starting another condition
983  // variable wait
984  for (;;) {
985  mutex.lock();
986  if (waiters) {
987  mutex.unlock();
988 #ifdef CGU_USE_SCHED_YIELD
989  sched_yield();
990 #else
991  usleep(10);
992 #endif
993  }
994  else {
995  mutex.unlock();
996  break;
997  }
998  }
999  if (full) datum->~T();
1000  std::free(datum);
1001  }
1002 
1004 };
1005 
1006 #endif // DOXYGEN_PARSING
1007 
1008 } // namespace Cgu
1009 
1010 #endif // CGU_ASYNC_CHANNEL_H
int broadcast()
Definition: mutex.h:483
A thread-safe "channel" class for inter-thread communication.
Definition: async_channel.h:201
STL namespace.
void close()
Definition: async_channel.h:259
A wrapper class for pthread condition variables.
Definition: mutex.h:449
bool move_pop(value_type &obj)
Definition: async_channel.h:566
bool pop(value_type &obj)
Definition: async_channel.h:476
A class enabling the cancellation state of a thread to be controlled.
Definition: thread.h:686
A scoped locking class for exception safe Mutex locking.
Definition: mutex.h:207
T value_type
Definition: async_channel.h:203
bool push(value_type &&obj)
Definition: async_channel.h:351
bool emplace(Args &&...args)
Definition: async_channel.h:414
A wrapper class for pthread mutexes.
Definition: mutex.h:117
int unlock()
Definition: mutex.h:170
AsyncChannel & operator=(const AsyncChannel &)=delete
Provides wrapper classes for pthread mutexes and condition variables, and scoped locking classes for ...
Definition: application.h:44
~AsyncChannel()
Definition: async_channel.h:676
std::size_t size_type
Definition: async_channel.h:204
AsyncChannel()
Definition: async_channel.h:648
void cgu_async_channel_waiters_dec(void *arg)
Definition: async_channel.h:104
int lock()
Definition: mutex.h:147
bool push(const value_type &obj)
Definition: async_channel.h:290
#define CGU_GLIB_MEMORY_SLICES_FUNCS
Definition: cgu_config.h:84
int wait(Mutex &mutex)
Definition: mutex.h:508