[/ (C) Copyright 2008 Oliver Kowalke. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt). ] [section:pool Pool] template< typename Channel, typename Fibers > class pool The pool manages internaly __worker_threads__ and submitted __actions__ get stored in the __global_queue__ (so called __channel__) for processing by the __worker_threads__ (using a __work_stealing__ algorithm). Each submitted __action__ gets associated with a __task__ object that will be returned. The __task__ object acts as a proxy for a result that is initially not known and gets evaluated later by a __worker_thread__. [heading Work-Stealing] Traditional thread pool doesn’t scale because there is a single __global_queue__ protected by a global lock. The frequency at which __worker_threads__ aquire the global lock becomes a limiting factor for the throughput if: * the __actions__ become smaller * more processors are added A work-stealing algorithm can be used to combat this problem. It uses a special kind of queue which has two ends, and allows lock-free pushes and pops from the ['private] end (accessed by the __worker_thread__ owning the queue), but requires synchronization from the ['public] end (accessed by the other __worker_threads__). Synchronization is necessary when the queue is sufficiently small that private and public operations could conflict. The pool contains one __global_queue__ (__bounded_channel__ or __unbounded_channel__) protected by a global lock each __worker_thread__ has its own private __worker_queue__. If work is enqueued by a __worker_thread__ the __action__ is stored in the __worker_queue__. If the work is enqueued by a application thread it goes into the __global_queue__. When __worker_threads__ are looking for work, they can have a preferred search order: * look into the private __worker_queue__ - __actions__ can be dequeued without locks * look in the __global_queue__ - locks are used for synchronization * check other __worker_queues__ ('stealing' __actions__ from private __worker_queues__ of other __worker_threads__) - requires locks For a lot of recursively queued __actions__, the use of a __worker_queue__ per thread substantially reduces the synchronization necessary to complete the work. There are also fewer cache effects due to sharing of the global queue information. Private operations on the private __worker_queue__ are executed in LIFO order and public operations on __worker_queues__ of other __worker_threads__ in FIFO order (steals). * There are chances that memory is still hot in the cache, if the __actions__ are pushed in LIFO order into the private __worker_queue__. * If a __worker_thread__ steals work in FIFO order, increases the chances that a larger 'chunk' of work will be stolen (the need for other steals will be possibly reduced). Because the __actions__ are stored in LIFO order, the oldest items are closer to the ['public] end of the queue (forming a tree). Stealing such an older __action__ also steals a (probably) larger subtree of __actions__ unfolded if the stolen work item get executed. [note __Actions__ submitted by a __worker_thread__ are stored into its private __worker_queue__ in LIFO order and thatswhy priorities and timeouts specified at the submit-function get ignored.] [important Because of the work-stealing algorithm the execution order of __actions__ may be not strict as in the __global_queue__.] [important Work-stealing will be disabled if __fibers__ are enabled on ['POSIX]-systems (['POSIX]-standard doesn't specify the interaction between fibers and threads).] [heading Pool creation] The first template argument specifies the channel type and the scheduling of __actions__. The second argument determins if __fibers__ are enabled or disabled (per default disabled). boost::tp::pool< boost::tp::unbounded_channel< boost::tp::fifo > > pool( boost::tp::poolsize( 6), boost::posix_time::posix_time::milliseconds( 50), boost::tp::scanns(10) ); In this example a __threadpool__ is created with a __unbounded_channel__ for scheduling __actions__ in ['FIFO] order. The pool contains six __worker_threads__ going to sleep for 50 millisec after 10 iterations without geting an __action__ from the __global_queue__, from its local __worker_queue__ or local queues of other __worker_threads__. boost::tp::pool< boost::tp::bounded_channel< boost::tp::priority < int > >, boost::tp::fibers_enabled > pool( boost::tp::poolsize( 10), boost::tp::high_watermark( 10), boost::tp::low_watermark( 5) ); The pool uses a __bounded_channel__ which schedules __actions__ by __priority__. A maximum of 10 __actions__ can be queued in the __global_queue__ without blocking the inserting thread. __fibers__ are enabled - allowing recursive execution of work. [heading Pool shutdown] The pool can be ['shutdown] - the the status of the pool is set to ['terminating] and all __worker_threads__ are joined. No futher __actions__ can be submitted by application threads. After all pending __actions__ are processed and all __worker_threads__ are joined, the pool is set to status terminated. [note The deconstructor calls ['shutdown] if the pool was not shutdown yet.] boost::tp::pool< boost::tp::unbounded_channel< boost::tp::fifo > > pool( boost::tp::poolsize( 1) ); boost::tp::task< int > t1( pool.submit( boost::bind( fibonacci_fn, 10) ) ); boost::tp::task< int > t2( pool.submit( boost::bind( fibonacci_fn, 10) ) ); pool.shutdown(); std::cout << t1.get() << std::endl; // 55 std::cout << t2.get() << std::endl; // 55 The function ['shutdown_now] sets te pool status to terminating interrupts and then joins all __worker_threads__. After the __worker_threads__ are joined the status of the pool is set to terminated and all pending (unprocessed) __actions__ will be returned. [important Pending __actions__ in the local __worker_queues__ are not returned.] [heading Meta informations] If the __threadpool__ supports priorities and which priority type is used can be evaluated my meta-functions `boost::tp::has_priority< pool_type >` and `boost::tp::priority_type< pool_type >`. The support of fibers can be tested with meta-function `boost::tp::has_fibers< pool_type >`. typedef boost::tp::pool< boost::tp::unbounded_channel< boost::tp::priority< int > > > pool_type; std::cout << std::boolalpha << boost::tp::has_priority< pool_type >::value << std::endl; std::cout << typeid( boost::tp::priority_type< pool_type >::type).name() << std::endl; std::cout << std::boolalpha << boost::tp::has_fibers< pool_type >::value << std::endl; [endsect]