seeking commonality in futures/threader/demuxer/defer/etc models

Q. Is it possible to isolate a core thread execution model from the various libs currently on offer, and re-express those libs in terms of the core model? The core service amonst these libraries is "Execution of abstracted tasks in an appropriate threads" Task seems to be in common expressible as a function object Major extension/variations include - waitable return object (future/threader) vs. ad hoc reverse direction task execution (asio::demuxer/defer) - threads resources shareable amongst different tasks (threader/demuxer/defer) vs thread created per action (futures, also possible with defer & threader styles) - some guarantees given about which thread will do a given task (threader/demuxer/defer) vs guaranteed new thread (future) - able to extend callbacks into windows message pump (defer) I think the different return styles can be unified to some extent by wrapper objects which operate like a void(void) function for exection, but which package up a wrapped function with return values and expose a wait() method on which the caller can wait for the result (essentially what threader/futures do) Futures autocreation of threads for each task is clearly implementable on top of any of the others, though perhaps not as efficiently. Guarantees about how a task will execute may not matter to all applications but it critical to others. The asio::demuxer gives a guarantee that the task will execute in a thread that has called demuxer::run, which may include the thread which is doing the requesting - implication being that the call may take place immediately. This is an excellent performance optimisation, but the programmer may require stronger guarantees, perhaps against that very optimisation. Vague Conclusions. I think its doable, I'd like to use such a boost library, i'd like it to be flexible in terms of strategising the thread allocation, i'd like to be able to use different models of thread allocator to solve different problems from distruting load amongst threads to solving thread deadlock issues to integrating my networking library with my GUI application. I'd prefer it if someone smarter than myself could write the code, but if they'd keep my requirements in mind while doing so (and treat my defer submission as an extension to those requirements) then i'd be happy. Cheers Simon Meiklejohn

Hi Simon, Just to clarify... --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
Guarantees about how a task will execute may not matter to all applications but it critical to others. The asio::demuxer gives a guarantee that the task will execute in a thread that has called demuxer::run, which may include the thread which is doing the requesting - implication being that the call may take place immediately.
Immediate execution can only occur if demuxer::dispatch() is used.
This is an excellent performance optimisation, but the programmer may require stronger guarantees, perhaps against that very optimisation.
Hence the distinction between demuxer::dispatch() which allows the optimisation, and demuxer::post() which does not. The decision about which is appropriate needs to be made at the point where the function object is invoked, so the two functions are provided. Cheers, Chris

Hi Chris,
The asio::demuxer gives a guarantee that the task will execute in a thread that has called demuxer::run, which may include the thread which is doing the requesting - implication being that the call may take place immediately.
Immediate execution can only occur if demuxer::dispatch() is used.
Ok, i hadnt picked that up.
This is an excellent performance optimisation, but the programmer may require stronger guarantees, perhaps against that very optimisation.
Hence the distinction between demuxer::dispatch() which allows the optimisation, and demuxer::post() which does not. The decision about which is appropriate needs to be made at the point where the function object is invoked, so the two functions are provided.
I see the distinction and appreciate the usefulness. I keep coming across situations though where i'd prefer the decision to hidden behind a defer object provided by some part of the code with a better context for the decision. (i.e. main() populating my network callback library with a defer object appropriate to the application). As an example, lets say i have a library component that parses data from a socket into some higher level application message. At the time it finds a complete message it wants to notify its client code. - In some applications its appropriate to call immediately in the same thread. (e.g. if the app has only one thread which is blocking in the network layer) - In other cases its appropriate to defer to a single different thread (eg. one with particular thread affinity, or the single thread that services all calls into a particular group of application objects thus providing protection against deadlocks) - in a third case its better to pass the message off to a pool of threads for performance/responsiveness reasons (e.g. the task involves accesses to a database which take time and can be done in parallel). The message parse library can be built to support all these scenarios. Just supply it with a different defer object when constructing and connecting the application objects. Hide the decision behind a polymorphically implemented demuxer::post(). Do these seem like useful general scenarios, and can they be supported with the demuxer interface as it stands? Many thanks Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote: <snip>
As an example, lets say i have a library component that parses data from a socket into some higher level application message. At the time it finds a complete message it wants to notify its client code.
I think this example perfectly illustrates why there is a difference between the needs of the caller and callee...
- In some applications its appropriate to call immediately in the same thread. (e.g. if the app has only one thread which is blocking in the network layer)
This first case is about the caller, since the caller best knows whether it is appropriate to make the call immediately in the same thread. E.g. an immediate call might no longer be appropriate if the library was changed to use asynchronous I/O so it could handle multiple concurrent connections, since blocking on the call would affect the service provided to other client code.
- In other cases its appropriate to defer to a single different thread (eg. one with particular thread affinity, or the single thread that services all calls into a particular group of application objects thus providing protection against deadlocks)
In this case you are talking about the needs of the callee, i.e. you talk about the threads that service calls *into* application objects.
- in a third case its better to pass the message off to a pool of threads for performance/responsiveness reasons (e.g. the task involves accesses to a database which take time and can be done in parallel).
This is about the needs of the caller again, since it does not want a long running operation to block it.
The message parse library can be built to support all these scenarios. Just supply it with a different defer object when constructing and connecting the application objects. Hide the decision behind a polymorphically implemented demuxer::post().
The decision can't be hidden entirely behind such a beast, although it may be part of the solution. Let's say the parser library has the following definition: class parser { ... void register_callback(function<void(int)> callback); ... function<void(int)> callback_; }; If the parser, as caller, decides it is appropriate to call the function directly then it need only go: callback_(42); However if the caller requires that the call be deferred then it can use an implementation of the dispatcher concept, such as asio::demuxer: parser_dispatcher_.post(boost::bind(callback_, 42)); The deferral needs of the client are decoupled. If the client does not care what thread calls it, then it can go: parser.register_callback(my_callback); However if the client has specific needs (such as that the calls must come in only on a specific thread) then it would use its own dispatcher to provide those guarantees: parser.register_callback(client_dispatcher_.wrap(my_callback)); This is where the optimised dispatch() call (versus always-deferring post() function) comes into play. The client code is not aware of the parser's deferral decision and vice versa. However they may in fact share the same dispatcher (such as an application-wide asio::demuxer object), in which case you want the callback to be optimised into a single deferral. The thing that asio does not provide is a polymorphic wrapper for the dispatcher concept so that the choice of deferral mechanism is done at runtime. However assuming one exists (and it wouldn't be hard to create one) the parser interface might be: class parser { ... parser(polymorphic_dispatcher& d) : parser_dispatcher_(d) {} ... void register_callback(function<void(int)> callback); ... polymorphic_dispatcher& parser_dispatcher_; function<void(int)> callback_; ... }; Specifying what dispatcher the parser should use is separate to supplying a callback function. It is still the parser's decision as to whether it requires a deferred call using post() or not. Cheers, Chris

Hi Chris,
--- simon meiklejohn <simon@simonmeiklejohn.com> wrote: <snip>
As an example, lets say i have a library component that parses data from a socket into some higher level application message. At the time it finds a complete message it wants to notify its client code.
I think this example perfectly illustrates why there is a difference between the needs of the caller and callee...
I guess i'm coming from the perspective that casts a message parsing library as purely a recogniser of certain kinds of message. As such it is more like a layer in a protocol stack rather than an application level 'caller'. In my analysis (which i dont claim to be universal) i'd say that the party with the best context for deciding the dispatcher policy for the parser is the party that assembles the protocol stack, not the layers themselves. (and i think that a given recogniser could usefully be deployed in a range of programs with different thread archtitectures) They know what meaning the messages have, how long it will take to deal with each of them, how complex the downstream web of receiving objects is, what else is doing callbacks into those objects what kinds of thread affinity is required in the recipient objects, .. all of which have a bearing on choosing a dispatcher.
- In some applications its appropriate to call immediately in the same thread. (e.g. if the app has only one thread which is blocking in the network layer)
This first case is about the caller, since the caller best knows whether it is appropriate to make the call immediately in the same thread. E.g. an immediate call might no longer be appropriate if the library was changed to use asynchronous I/O so it could handle multiple concurrent connections, since blocking on the call would affect the service provided to other client code.
Could this be handled by the caller specifying its requirement for defer/dispatcher through my proposed-but-not-implemented defer_point categorisations e.g. class immediate_defer : public defer_point {}; // no implementation, just a promise on how defer will happen class defer_null : public immediate_defer { /* implementation as in sandbox */ }; or also class defer_mutex : public immediate_defer { /* ditto */}; class parser { parser( immediate_defer& dispatcher ) : parser_dispatch_(parser_dispatch) {} void register_callback(function<void(void)callback); ... some_method() { parser_dispatch.defer( callback_ ); // will happen now } } the developer is then forced to use a dispatcher of appropriate type. He can decide whether the recipient needs mutex protection or not. [SNIP] Splitting this response over a couple of messages, otherwise just too long Cheers Simon

Long post - continued.
The deferral needs of the client are decoupled. If the client does not care what thread calls it, then it can go:
parser.register_callback(my_callback);
However if the client has specific needs (such as that the calls must come in only on a specific thread) then it would use its own dispatcher to provide those guarantees:
parser.register_callback(client_dispatcher_.wrap(my_callback));
This is where the optimised dispatch() call (versus always-deferring post() function) comes into play. The client code is not aware of the parser's deferral decision and vice versa. However they may in fact share the same dispatcher (such as an application-wide asio::demuxer object), in which case you want the callback to be optimised into a single deferral.
there is some subtle and clever stuff going on there (particularly the last optimisation). I'm not sure if it works out as more efficient though than the programmer/integrator making a single policy decision conveyed to the originator of the callback (the parser) about how to do the callback. The one case where yours is clearly more efficient is the parser not doing a defer, but calling the callback object immediately, and that object being a non-wrapped ordinary function object.
The thing that asio does not provide is a polymorphic wrapper for the dispatcher concept so that the choice of deferral mechanism is done at runtime. However assuming one exists (and it wouldn't be hard to create one) the parser interface might be:
class parser { ... parser(polymorphic_dispatcher& d) : parser_dispatcher_(d) {} ... void register_callback(function<void(int)> callback); ... polymorphic_dispatcher& parser_dispatcher_; function<void(int)> callback_; ... };
I agree - that looks entirely reasonable.
Specifying what dispatcher the parser should use is separate to supplying a callback function. It is still the parser's decision as to whether it requires a deferred call using post() or not.
As discussed, i think it can be expressed entirely via post() by kicking the post()/dispatch() decision upstairs to main(). Thanks again for responding to my posts. Hope the discussion is useful feedback for the asio review process. Simon

Long post - continued.
The deferral needs of the client are decoupled. If the client does not care what thread calls it, then it can go:
parser.register_callback(my_callback);
However if the client has specific needs (such as that the calls must come in only on a specific thread) then it would use its own dispatcher to provide those guarantees:
parser.register_callback(client_dispatcher_.wrap(my_callback));
This is where the optimised dispatch() call (versus always-deferring post() function) comes into play. The client code is not aware of the parser's deferral decision and vice versa. However they may in fact share the same dispatcher (such as an application-wide asio::demuxer object), in which case you want the callback to be optimised into a single deferral.
there is some subtle and clever stuff going on there (particularly the last optimisation). I'm not sure if it works out as more efficient though than the programmer/integrator making a single policy decision conveyed to the originator of the callback (the parser) about how to do the callback. The one case where yours is clearly more efficient is the parser not doing a defer, but calling the callback object immediately, and that object being a non-wrapped ordinary function object.
The thing that asio does not provide is a polymorphic wrapper for the dispatcher concept so that the choice of deferral mechanism is done at runtime. However assuming one exists (and it wouldn't be hard to create one) the parser interface might be:
class parser { ... parser(polymorphic_dispatcher& d) : parser_dispatcher_(d) {} ... void register_callback(function<void(int)> callback); ... polymorphic_dispatcher& parser_dispatcher_; function<void(int)> callback_; ... };
I agree - that looks entirely reasonable.
Specifying what dispatcher the parser should use is separate to supplying a callback function. It is still the parser's decision as to whether it requires a deferred call using post() or not.
As discussed, i think it can be expressed entirely via post() by kicking the post()/dispatch() decision upstairs to main(). Thanks again for responding to my posts. Hope the discussion is useful feedback for the asio review process. Simon

http://boost-consulting.com/vault/ under concurrent programming. the previous boost website main page used to link to this area under the nsme sandbox files. but now its under the name Vault Files. Cheers Simon
participants (2)
-
Christopher Kohlhoff
-
simon meiklejohn