BDE 4.39.x Production Release
Loading...
Searching...
No Matches
bdlmt_threadmultiplexor.h
Go to the documentation of this file.
1/// @file bdlmt_threadmultiplexor.h
2///
3/// The content of this file has been pre-processed for Doxygen.
4///
5
6
7// bdlmt_threadmultiplexor.h -*-C++-*-
8#ifndef INCLUDED_BDLMT_THREADMULTIPLEXOR
9#define INCLUDED_BDLMT_THREADMULTIPLEXOR
10
11#include <bsls_ident.h>
12BSLS_IDENT("$Id: $")
13
14/// @defgroup bdlmt_threadmultiplexor bdlmt_threadmultiplexor
15/// @brief Provide a mechanism for partitioning a collection of threads.
16/// @addtogroup bdl
17/// @{
18/// @addtogroup bdlmt
19/// @{
20/// @addtogroup bdlmt_threadmultiplexor
21/// @{
22///
23/// <h1> Outline </h1>
24/// * <a href="#bdlmt_threadmultiplexor-purpose"> Purpose</a>
25/// * <a href="#bdlmt_threadmultiplexor-classes"> Classes </a>
26/// * <a href="#bdlmt_threadmultiplexor-description"> Description </a>
27/// * <a href="#bdlmt_threadmultiplexor-thread-safety"> Thread Safety </a>
28/// * <a href="#bdlmt_threadmultiplexor-order-of-execution"> Order of Execution </a>
29/// * <a href="#bdlmt_threadmultiplexor-usage"> Usage </a>
30/// * <a href="#bdlmt_threadmultiplexor-example-1-multiple-work-queues"> Example 1: Multiple Work Queues </a>
31///
32/// # Purpose {#bdlmt_threadmultiplexor-purpose}
33/// Provide a mechanism for partitioning a collection of threads.
34///
35/// # Classes {#bdlmt_threadmultiplexor-classes}
36///
37/// - bdlmt::ThreadMultiplexor: mechanism to partition multi-threaded processing
38///
39/// @see bdlmt_threadpool, bdlmt_fixedthreadpool
40///
41/// # Description {#bdlmt_threadmultiplexor-description}
42/// This component provides a mechanism for partitioning a
43/// collection of threads, so that a single collection of threads (e.g., a
44/// thread pool) can be used to process various types of user-defined functions
45/// ("jobs") while sharing the thread resources equitably between them. A
46/// typical example where this type of partitioning is desired is an application
47/// that performs both I/O and CPU-intensive processing. The traditional
48/// approach is to create two thread pools--one for I/O, and one for
49/// processing--and pass control (in the form of a callback) from one thread
50/// pool to the other. However, there are several problems with this approach.
51/// Firstly, the process incurs the overhead of context switching between
52/// threads, which must necessarily occur because there are two different thread
53/// pools. Secondly, the process may not be able to adapt well to imbalances
54/// between one type of processing versus the other if the number of threads in
55/// each thread pool is bounded. In this case, a large number of jobs may be
56/// enqueued while some portion of threads allocated to the process go unused.
57/// On the other hand, simply sharing a single thread pool without a provision
58/// for partitioning the use of threads may result in one type of processing
59/// starving the other.
60///
61/// The `bdlmt::ThreadMultiplexor` provides an API, `processJob`, to process
62/// user-specified jobs. A multiplexor instance is configured with a maximum
63/// number of "processors", i.e., the maximum number of threads that may process
64/// jobs at any particular time. Additional threads enqueue jobs to a pending
65/// job queue, which is processed by the next available processing thread.
66///
67/// Typically, a `bdlmt::ThreadMultiplexor` instance is used in conjunction with
68/// a thread pool (e.g., `bdlmt::FixedThreadPool`), where each thread pool
69/// thread calls the multiplexor `processJob` method to perform some work. The
70/// multiplexor guarantees that no more that the configured number of threads
71/// will process jobs concurrently. This guarantee allows a single thread pool
72/// to be used in a variety of situations that require partitioning thread
73/// resources.
74///
75/// ## Thread Safety {#bdlmt_threadmultiplexor-thread-safety}
76///
77///
78/// The `bdlmt::ThreadMultiplexor` class is both **fully thread-safe** (i.e.,
79/// all non-creator methods can correctly execute concurrently), and is
80/// **thread-enabled** (i.e., the class does not function correctly in a
81/// non-multi-threading environment). See @ref bsldoc_glossary for complete
82/// definitions of **fully thread-safe** and **thread-enabled**.
83///
84/// ## Order of Execution {#bdlmt_threadmultiplexor-order-of-execution}
85///
86///
87/// 'bdlmt::ThreadMultiplexor' does not guarantee that jobs in the pending job
88/// queue will be processed in the order in which they were enqueued.
89///
90/// ## Usage {#bdlmt_threadmultiplexor-usage}
91///
92///
93/// This section illustrates intended use of this component.
94///
95/// ### Example 1: Multiple Work Queues {#bdlmt_threadmultiplexor-example-1-multiple-work-queues}
96///
97///
98/// The following usage example illustrates how the `bdlmt::ThreadMultiplexor`
99/// can be used to share thread resources between three separate work queues.
100/// Assume that there are three classes of jobs: jobs that are important, jobs
101/// that are urgent, and jobs that are critical. We would like to execute each
102/// class of jobs in a single thread pool, but we want ensure that all types of
103/// jobs can be executed at any time.
104///
105/// We begin by defining a class that encapsulates the notion of a job queue.
106/// Our `JobQueue` class holds a reference to a `bdlmt::FixedThreadPool`, used
107/// to instantiate the job queue, and owns an instance of
108/// `bdlmt::ThreadMultiplexor`, used to process jobs.
109/// @code
110/// /// This class defines a generic processor for user-defined functions
111/// /// ("jobs"). Jobs specified to the `processJob` method are executed
112/// /// in the thread pool specified at construction.
113/// class JobQueue {
114///
115/// public:
116/// // PUBLIC TYPES
117///
118/// /// A callback of this type my be specified to the `processJob` method.
119/// typedef bdlmt::ThreadMultiplexor::Job Job;
120///
121/// private:
122/// // DATA
123/// bdlmt::FixedThreadPool *d_threadPool_p; // (held, not owned)
124/// bdlmt::ThreadMultiplexor d_multiplexor; // used to partition threads
125///
126/// private:
127/// // NOT IMPLEMENTED
128/// JobQueue(const JobQueue&);
129/// JobQueue& operator=(const JobQueue&);
130///
131/// public:
132/// // CREATORS
133///
134/// /// Create a job queue that executes jobs in the specified
135/// /// 'threadPool' using no more than the specified 'maxProcessors'.
136/// /// Optionally specify a 'basicAllocator' used to supply memory. If
137/// /// 'basicAllocator' is 0, the currently installed default allocator
138/// /// is used.
139/// JobQueue(int maxProcessors,
140/// bdlmt::FixedThreadPool *threadPool,
141/// bslma::Allocator *basicAllocator = 0);
142///
143/// /// Destroy this object.
144/// ~JobQueue();
145///
146/// // MANIPULATORS
147///
148/// /// Process the specified `job` in the thread pool specified at
149/// /// construction. Return 0 on success, and a non-zero value otherwise.
150/// int processJob(const Job& job);
151/// };
152/// @endcode
153/// The maximum number of processors for the multiplexor instance owned by each
154/// `JobQueue` is configured using the following formula, for
155/// T = number of threads and M = number of multiplexors > 1:
156/// @code
157/// maxProc = ceil(T / (M-1))-1
158/// @endcode
159/// This allows multiple `JobQueue` instances to share the same threadpool
160/// without starving each other when the thread pool has more than one thread.
161/// For this usage example, we assume M (number of multiplexors) = 3, and T
162/// (number of threads) = 5, so maxProc = 2. It is important to note that every
163/// call to `processJob` enqueues a job to the thread pool, so the length of the
164/// thread pool queue determines the maximum number of jobs that can be accepted
165/// by the JobQueue. (Multiple JobQueues share the same maximum *together*, so
166/// not all will be able to reach their individual maximum at the same time).
167/// @code
168///
169/// JobQueue::JobQueue(int maxProcessors,
170/// bdlmt::FixedThreadPool *threadPool,
171/// bslma::Allocator *basicAllocator)
172/// : d_threadPool_p(threadPool)
173/// , d_multiplexor (maxProcessors,
174/// threadPool->queueCapacity(),
175/// basicAllocator)
176/// {
177/// }
178///
179/// JobQueue::~JobQueue()
180/// {
181/// }
182/// @endcode
183/// The `processJob` method enqueues a secondary callback into the thread pool
184/// that executes the user-specified `job` through the multiplexor.
185/// @code
186/// int JobQueue::processJob(const JobQueue::Job& job)
187/// {
188/// return d_threadPool_p->tryEnqueueJob(bdlf::BindUtil::bind(
189/// &bdlmt::ThreadMultiplexor::processJob<Job>,
190/// &d_multiplexor,
191/// job));
192/// }
193/// @endcode
194/// The following program uses three instances of `JobQueue` to process
195/// important, urgent, and critical jobs using a single collection of threads.
196/// @code
197/// int main(void)
198/// {
199/// enum {
200/// NUM_THREADS = 5, // total number of threads
201/// NUM_QUEUES = 3, // total number of JobQueue objects
202/// MAX_QUEUESIZE = 20 // total number of pending jobs
203/// };
204///
205/// int maxProc = bsl::max(1,
206/// ceil(double(NUM_THREADS) / (NUM_QUEUES-1))-1);
207///
208/// bdlmt::FixedThreadPool tp(NUM_THREADS, MAX_QUEUESIZE);
209/// JobQueue importantQueue(maxProc, &tp);
210/// JobQueue urgentQueue(maxProc, &tp);
211/// JobQueue criticalQueue(maxProc, &tp);
212///
213/// if (0 != tp.start()) {
214/// ASSERT(0 == "Could not start thread pool!");
215/// return -1;
216/// }
217///
218/// JobQueue::Job ijob =
219/// bdlf::BindUtil::bind(&bsls::AtomicInt::add, &iCheck, 1);
220///
221/// JobQueue::Job ujob = bdlf::BindUtil::bind(
222/// bdlf::BindUtil::bind(&bsls::AtomicInt::add, &uCheck, 1);
223///
224/// JobQueue::Job cjob = bdlf::BindUtil::bind(
225/// bdlf::BindUtil::bind(&bsls::AtomicInt::add, &cCheck, 1);
226///
227/// importantQueue.processJob(ijob);
228/// importantQueue.processJob(ijob);
229/// importantQueue.processJob(ijob);
230/// importantQueue.processJob(ijob);
231/// importantQueue.processJob(ijob);
232/// importantQueue.processJob(ijob);
233///
234/// urgentQueue.processJob(ujob);
235/// urgentQueue.processJob(ujob);
236/// urgentQueue.processJob(ujob);
237/// urgentQueue.processJob(ujob);
238///
239/// criticalQueue.processJob(cjob);
240/// criticalQueue.processJob(cjob);
241///
242/// tp.stop();
243/// ASSERT(6 == iCheck);
244/// ASSERT(4 == uCheck);
245/// ASSERT(2 == cCheck);
246/// return 0;
247/// }
248/// @endcode
249/// @}
250/** @} */
251/** @} */
252
253/** @addtogroup bdl
254 * @{
255 */
256/** @addtogroup bdlmt
257 * @{
258 */
259/** @addtogroup bdlmt_threadmultiplexor
260 * @{
261 */
262
263#include <bdlscm_version.h>
264
265#include <bdlcc_fixedqueue.h>
266
267#include <bsls_atomic.h>
268
269#include <bslma_allocator.h>
271
273
274#include <bsl_functional.h>
275
276#ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES
277#include <bslalg_typetraits.h>
278#endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES
279
280
281
282namespace bdlmt {
283 // =======================
284 // class ThreadMultiplexor
285 // =======================
286
287/// This class provides a mechanism for facilitating the use of multiple
288/// threads to perform various user-defined functions ("jobs") when some
289/// degree of collaboration between threads is required. The thread
290/// multiplexor is configured with a total number of "processors",
291/// representing the number of threads that may process jobs at any
292/// particular time. Additional threads enqueue jobs to a pending job
293/// queue, which is processed by the next available processing thread.
294///
295/// See @ref bdlmt_threadmultiplexor
297
298 public:
299 // PUBLIC TYPES
300
301 /// A callback of this type may be passed to the `processJob` method.
302 typedef bsl::function<void()> Job;
303
304 private:
305 // DATA
306 bslma::Allocator *d_allocator_p; // memory allocator (held)
307 bdlcc::FixedQueue<Job> *d_jobQueue_p; // pending job queue (owned)
308 bsls::AtomicInt d_numProcessors; // current number of processors
309 int d_maxProcessors; // maximum number of processors
310
311 private:
312 // PRIVATE MANIPULATORS
313
314 /// Process the pending job queue. Execute each functor obtained from
315 /// the queue in the calling thread if the current number of processors
316 /// is less than the maximum number of processors. Otherwise, enqueue
317 /// the job back to the pending queue. Return 0 on success, and a
318 /// non-zero value otherwise.
319 int processJobQueue();
320
321 private:
322 // NOT IMPLEMENTED
324 ThreadMultiplexor& operator=(const ThreadMultiplexor&);
325
326 public:
327 // TRAITS
330
331 // CREATORS
332
333 /// Create a thread multiplexor which uses, at most, the specified
334 /// `maxProcessors` number of threads to process user-specified jobs,
335 /// identified as callbacks of type `Job`. Jobs that cannot be processed
336 /// immediately are placed on a queue having the specified `maxQueueSize`
337 /// to be processed by the next free thread. Optionally specify a
338 /// `basicAllocator` used to supply memory. If `basicAllocator` is 0, the
339 /// currently installed default allocator is used.
340 ///
341 /// \pre The behavior is undefined unless `0 < maxProcessors` and `0 < maxQueueSize`.
343 int maxQueueSize,
344 bslma::Allocator *basicAllocator = 0);
345
346 /// Destroy this thread multiplexor object.
348
349 // MANIPULATORS
350
351 /// Process the specified `job` functor in the calling thread if the
352 /// current number of processors is less than the maximum number of
353 /// processors. Otherwise, enqueue `job` to the pending job queue. Return 0 on success, and a non-zero value otherwise.
354 ///
355 /// \note Note that the only
356 /// requirements on `t_JOBTYPE` are that it defines `operator()`, having a
357 /// `void` return type, and that it defines a copy constructor.
358 template <class t_JOBTYPE>
359 int processJob(const t_JOBTYPE& job);
360
361 // ACCESSORS
362
363 /// Return the maximum number of active processors.
364 int maxProcessors() const;
365
366 /// Return the current number of active processors.
367 int numProcessors() const;
368};
369
370// ============================================================================
371// INLINE DEFINITIONS
372// ============================================================================
373
374// MANIPULATORS
375template <class t_JOBTYPE>
376inline
377int ThreadMultiplexor::processJob(const t_JOBTYPE& job)
378{
379 // Execute 'job' in the calling thread if the current number of processors
380 // is less than the maximum number of processors. Otherwise, enqueue 'job'
381 // to the pending job queue. In all cases, check the pending job queue at
382 // the end of the loop, as the number of processors may have changed,
383 // allowing the execution of a job in the current thread.
384
385 int previousNumProcessors = d_numProcessors;
386 if (previousNumProcessors < d_maxProcessors &&
387 previousNumProcessors ==
388 d_numProcessors.testAndSwap(previousNumProcessors,
389 previousNumProcessors + 1)) {
390 // Process the job
391 job();
392 --d_numProcessors;
393 }
394 else {
395 int rc = d_jobQueue_p->tryPushBack(job);
396 if (0 != rc) {
397 return rc; // RETURN
398 }
399 }
400
401 return processJobQueue();
402}
403
404// ACCESSORS
405inline
407{
408 return d_maxProcessors;
409}
410
411inline
413{
414 return d_numProcessors;
415}
416
417} // close package namespace
418
419#endif
420
421// ----------------------------------------------------------------------------
422// Copyright 2015 Bloomberg Finance L.P.
423//
424// Licensed under the Apache License, Version 2.0 (the "License");
425// you may not use this file except in compliance with the License.
426// You may obtain a copy of the License at
427//
428// http://www.apache.org/licenses/LICENSE-2.0
429//
430// Unless required by applicable law or agreed to in writing, software
431// distributed under the License is distributed on an "AS IS" BASIS,
432// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
433// See the License for the specific language governing permissions and
434// limitations under the License.
435// ----------------------------- END-OF-FILE ----------------------------------
436
437
438/** @} */
439/** @} */
440/** @} */
Definition bdlcc_fixedqueue.h:274
Definition bdlmt_threadmultiplexor.h:296
~ThreadMultiplexor()
Destroy this thread multiplexor object.
int processJob(const t_JOBTYPE &job)
Definition bdlmt_threadmultiplexor.h:377
int maxProcessors() const
Return the maximum number of active processors.
Definition bdlmt_threadmultiplexor.h:406
ThreadMultiplexor(int maxProcessors, int maxQueueSize, bslma::Allocator *basicAllocator=0)
bsl::function< void()> Job
A callback of this type may be passed to the processJob method.
Definition bdlmt_threadmultiplexor.h:302
BSLMF_NESTED_TRAIT_DECLARATION(ThreadMultiplexor, bslma::UsesBslmaAllocator)
int numProcessors() const
Return the current number of active processors.
Definition bdlmt_threadmultiplexor.h:412
Forward declaration.
Definition bslstl_function.h:946
Definition bslma_allocator.h:545
Definition bsls_atomic.h:744
int testAndSwap(int compareValue, int swapValue)
Definition bsls_atomic.h:1723
#define BSLS_IDENT(str)
BSLS_IDENT() - insert string into .comment binary segment (if supported)
Definition bsls_ident.h:238
Definition bdlmt_eventscheduler.h:550
Definition bslma_usesbslmaallocator.h:344