[threadpool] draft n2276 - launch_in_pool

Hello, I did a look into draft n2276 - a launch_in_pool function is suggested (passing task to a system provided threadpool). Is it reasonable to have a static threadpool? How many worker-threads should this pool contain and how are the submitted tasks queued (FIFO, priority order ... unlimited/limited number of tasks can bes stored). What about applications which require a pool with different configuration than the static pool provides? Is it acceptable to pay for an unused pool? regards, Oliver -- Psssst! Schon vom neuen GMX MultiMessenger gehört? Der kann`s mit allen: http://www.gmx.net/de/go/multimessenger

Hi Oliver, I'm not the author of the draft (nor am I part of the standards committe) but I think I have some answers to your questions below. On Fri, Nov 21, 2008 at 9:55 PM, Oliver Kowalke <k-oli@gmx.de> wrote:
I think with the changes on the standards regarding function-local static variables, I think it's alright.
How many worker-threads should this pool contain and how are the submitted tasks queued (FIFO, priority order ... unlimited/limited number of tasks can bes stored).
I would think it should be customizable either as a compilation flag (compile-time macros) or through overloads to the function.
What about applications which require a pool with different configuration than the static pool provides?
It should be feasible to define through compile-time macros or even with function overloads.
Is it acceptable to pay for an unused pool?
You shouldn't have to -- especially if you don't call the function. I'm thinking the function should be using a local static pool anyway, which gets initialized (if called) before the thread for main() is started. HTH -- Dean Michael C. Berris Software Engineer, Friendster, Inc.

Hi! A static threadpool is not inherently unreasonable, but only supporting a single instance of threadpool is. The optimal number of worker-threads in the thread pool depends on the problem and is not always equal to the number of available cores. An example of a case where I get better performance running more threads than available cores is where I have synchronous I/O on each of the threads that means that on average 2 out of 4 threads are in the blocked state. It is measurably better to add two more threads into the thread pool and allow the CPU to be optimally utilised. There are clearly design alternatives using asynchronous I/O etc. but this is merely intended as an example. Since the optimal number of threads depends on the tasks performed on the threads it follows that only having one thread-pool with one configuration per process is a bad idea. I hope this helps, Neil Groves On Fri, Nov 21, 2008 at 1:55 PM, Oliver Kowalke <k-oli@gmx.de> wrote:

Hi Neil, On Fri, Nov 21, 2008 at 10:01 PM, Neil Groves <neil@grovescomputing.com> wrote:
I agree, to some extent. Let me defer why I think so later.
Again I agree to some extent. However, it's not the standard's job I think to optimize the tasks that you're running in the thread pool anyway -- I think there are a lot of ways to make the thread pools more effective/efficient (through work-stealing on waits, etc.) but that's only for an efficient implementation of a thread pool.
True. But I wouldn't be surprised if there would be a proposal to an overload to the launch_in_pool(...) method which takes a "concrete" or "existing" pool that is either referred to by a handle/proxy or by reference. Something like: // version 1 template <ThreadPool pool_type, PackagedTask task_type> typename task_type::future_type launch_in_pool(task_type task, pool_type & pool) { pool.post(task); return task.get_future(); } // version 2 template <PackagedTask task_type> typename task_type::future_type launch_in_pool(task_type task) { static std::default_thread_pool __pool_instance; __pool_instance.post(task); return task.get_future(); } Above, version 1 would have an overload that takes a reference to an existing thread pool to which a task gets posted to. Also, version 2 will use a static local pool, intialized before main() is called by the main thread, and destroyed when main() finishes. I hope this makes sense. -- Dean Michael C. Berris Software Engineer, Friendster, Inc.

Hi Dean,
sorry, but i don't see the benefit Maybe some additional functions are required which create customized pool accessible over launch_in_pool. Singleton from Andrei Alexandrescu comes into my mind (tracks the order of creation/destruction of singletons) - a implementaion was announced some months ago in the mailing list. regards, Oliver -- Sensationsangebot nur bis 30.11: GMX FreeDSL - Telefonanschluss + DSL für nur 16,37 Euro/mtl.!* http://dsl.gmx.de/?ac=OM.AD.PD003K11308T4569a

On Fri, Nov 21, 2008 at 10:25 PM, Oliver Kowalke <k-oli@gmx.de> wrote:
The version you quoted allowed you to create your own thread pool and pass that in as the second parameter to the launch_in_pool(...) function. That means, any type that models the ThreadPool concept should qualify as a thread pool. If you want a special type of thread pool with a different configuration from the default (as presented in version 2) that's controllable using compile-time definitions (for strategies used, etc.) then you'll just have to write your own thread pool implementation and make sure you write a concept_map to adapt your implementation to the ThreadPool concept. Of course this assumes three things: 1. The ThreadPool concept is defined. 2. The local static initialization and destruction sequence is standardized across vendors as what the C++0x standard proposes. 3. The implementation of the standard thread pool is defined by the standard and not vendor-specific. In case 3, then vendors may be able to give more options about how the default thread pool can be configured at compile time. If all else fails, version 1 (which you have quoted) will be the implementation that you can use -- write your own or use an existing thread pool adapted to the ThreadPool concept. -- Dean Michael C. Berris Software Engineer, Friendster, Inc.

----- Original Message ----- From: "Dean Michael Berris" <mikhailberis@gmail.com> To: <boost@lists.boost.org> Sent: Friday, November 21, 2008 3:15 PM Subject: Re: [boost] [threadpool] draft n2276 - launch_in_pool
Hi, I'm working on a generic Asynchronous Execution framework that defines a generic fork function as template< typename AE, typename Function > typename result_of::fork<AE,Function >::type fork( AE& ae, Function fn ); In my approach the result of the fork depends on the Asynchronous Executor, e.g. the result of forking with tm::pool<> will be tp::task<result_of<Function ()>::type>. Which are the minimal functions we expect from an Asynchronous Executor? * async_call : AE, Function -> Handle<result_of<Function()> evaluate a function asynchronously * lazy_call : AE, Function -> Handle<result_of<Function()> evaluates a function when needed * call_after_completion : AE, Handles, Function -> Handle<result_of<Function()> evaluates a function asynchronously after completion of all some handles Which are the minimal functions we expect from a Handle<T>? In my opinion they should follow the future and task interface * wait : Handle<T> * wait_until : Handle<T>, Time * wait_for : Handle<T>, Duration * get : Handle<T> -> Handle<T>::result_type * is_ready : Handle<T> -> bool * has_exception: Handle<T> -> bool * has_value: Handle<T> -> bool Handle must be Movable or CopyConstructible (or boths). Do we need Handles that are neither Movable nor CopyConstructible ? Some handles will move the result when calling get() (as do unique_future) while others will do copy (as do shared_future) so Handle<T>::result_type will depend on this feature. If we follow C++0x unique_future<T>::result_type = T&& unique_future<&T>::result_type = T& unique_future<void>::result_type = void shared_future<T>::result_type = const T& shared_future<&T>::result_type = T& shared_future<void>::result_type = void Some handles will allow to be interruped, * interrupt: Handle<T> * interrupt_requested : Handle<T> -> bool Others handles could be detached * detach: Handle<T> * joinable: Handle<T> -> bool On top of this we can add some functions that work on several functions * async_call_all : AE, F_1, ..., F_N -> HandleTuple evaluate N function asynchronously * wait_for_all : Tuple Handle's wait for the completion of all the handles * wait_for_any : Tuple Handle's -> unsigned wait for the completion of any of the handle in the tuple and return its index * get_all : Tuple Handle's -> ResultTuple wait for the completion of all the handles and return its results in a tuple. Or even * conc_wait_for_all : AE, F_1, ..., F_N -> ResultTuple evaluate N function asynchronously and returns its results in a tuple * conc_wait_for_any : AE, F_1, ..., F_N -> ResultVariant | Result evaluate N function asynchronously and returns once the first completes with its results in a variant of the result types or Result if all the result types are the same. Could we get a consensus on the minimal features? Any idea for better names for these Asynchronous Executor, and Asynchronous Completion Handle concepts, and the associated functions (async_call, fork, launch, spawn ...)? Thanks, Vicente P.S. The InterThreads library include a probe of concept of this Asynchronous Execution framework. It is available at Boost Sandbox: https://svn.boost.org/svn/boost/sandbox/interthreads and Boost Vault: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=interthreads.zip&directory=Concurrent%20Programming&

"Oliver Kowalke" <k-oli@gmx.de> writes:
I did a look into draft n2276 - a launch_in_pool function is suggested (passing task to a system provided threadpool).
Is it reasonable to have a static threadpool?
Yes.
In the proposal, that's up to the implementer to choose. The only requirement is that in the absence of coding errors, a submitted task will actually complete (i.e. the code should run as if each task was on its own thread, but possibly delayed in starting). This contrains the work-stealing algorithms in use, but allows the number of threads to be dynamic, and the order to be up to the implementation. If the pool cannot accept any more tasks (whether that's because there's a hard-coded limit, or because it runs out of memory) then the task submission should fail.
What about applications which require a pool with different configuration than the static pool provides?
Don't use the static pool. Alternatively provide overloads of launch_in_pool that allow task properties to be specified (e.g. "this thread spends lots of time doing blocking waits" => maybe schedule an extra thread).
Is it acceptable to pay for an unused pool?
It could be initialized on first use. Anthony -- Anthony Williams Author of C++ Concurrency in Action | http://www.manning.com/williams Custom Software Development | http://www.justsoftwaresolutions.co.uk Just Software Solutions Ltd, Registered in England, Company Number 5478976. Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK
participants (5)
-
Anthony Williams
-
Dean Michael Berris
-
Neil Groves
-
Oliver Kowalke
-
vicente.botet