RE: [boost] Re: Re: Future of threads (III)

On Behalf Of scott Subject: RE: [boost] Re: Re: Future of threads (III)
On Behalf Of Hurd, Matthew
AO's should either have a queue for work or hook into a queue. They are a bit like a worker pool with one worker ;-)
yep.
Another idiom is a future value, where you ask for a value and don't block until you "read" the value. ACE also has an implementation of this. You can think of it as a bit like overlapped I/O.
Yes. This was also in the ActiveObject pattern. It was on the "client- side", i.e. available to the thread that submits Method Requests as a means to acquire the results.
While I understand its place in the ActiveObject pattern I would hope that if a boost::active_object came into existence, there would be a single (i.e. not _simple_) mechanism by which data passed from one
thread
to the other, with an associated notification. In a behavioural sense, the future mechanism wouldnt be appropriate for this.
I'd agree here in principle to keep this asynchronous and have the equivalent of message passing with a function call with a void return type. The future_value is an elegant idiom for providing a C++ RPC / LPC equivalent. The whole store and forward expectation issue would suit this style as it just looks like a normal function or method call.
This way of thinking does require that all threads are active_objects which may be too aggressive <shrug>. A small sample of empirical data says its fine.
It works a treat for a lot of things. Especially suited to workflows of things that you want to dynamically configure. Threading is a big overhead though. Consider these numbers: Safe increment (Athlon 2800+) (P4 3.2GHz) Interlocked primitive 11 ns 53 ns Critical section 138 ns 411 ns A bit slower than a single machine instruction. If I make a size_t volatile and time 100 increments in a row I get 3.8ns per increment on the P4 above, more then ten times slower than an interlocked increment. And it doesn't end there, the cache effects also add considerably. So in my mind, the active object pattern is good for configurability and where the work of the object is chunky enough to where the cost of the context switches and cache effects. In terms of work pools, this context overhead also led to the idea of a leader-follower pattern where you try and use the same worker if possible. This is also why we have seen the argument for a shared_ptr with multi-thread safety as a policy. On a Pentium 4 you are typically paying around ten times the cost of a normal increment for the privilege of thread safety.
I also advocate a different architectural neutral style where you can have things at look like function calls that can be: 1. a function call 2. a call to queue work pool or active object 3. an IPC mechanism for same machine transfer, e.g. shared memory 4. a network mechanism, TCP, UDP, TIB or something, for
Understand the reasons you ask for 1...4 but would like to add some other thoughts to see what you think.
An ActiveObject servant contains code. That code is implementation of behaviour and its what (I believe) we tacitly want when we all say that "thread-inherited-classes" or "active objects" are a nice manner in which to deal with threads. (note: more accurately the pattern says that thread==scheduler and a scheduler executes servant code. but i think we can skip this detail)
If the code is in the servant, what do 1 and 2 mean? My best matching of your thinking with the servant-based thinking would be that somehow the "client" can submit something into the "worker pool" that _identifies_ a function that is in the servant. It would be nice if this "work submission" could carry some arguments for presentation to the servant.
I think I understand what you're saying, but I'm not sure. Are you thinking about how the marshalling is done?
A detail that I am labouring is the separation of "calling" (would much rather that this was "sending") and execution. Our active object has no synchronous i/f with methods for us to call; it is simply a "work pool" that we add to. As soon as we make a sync i/f available then we go down that road that leads to servant code in the client.
I suggest that notions of "function call" should be exorcised from discussions that are targeting active objects. That type of thinking is perfectly valid in some other thread (oops) but can scuttle work targeting active objects; the behaviour (i.e. the code) is inherently in the object.
Not sure I agree. Think of it the small talk way. A message is a method call. You could have your active object providing a function call that puts a message on to its internal queue and returns. This way you could perhaps specialise by policy whether or not it had its own thread of control and make your architecture a lot more flexible similar to the relationship of 1. and 2. above.
a client can only submit "worker orders" or "Method Requests" or jobs or messages or signals.
I hope there is enough there to make a case :-)
You certainly have a case. The nirvana I'm looking for is a policy approach to reconfiguring architecture so I can make late decisions about single, multi-thread, multi-process, distributed design. I think your active object can gel with this as I think you can consider parallels to the numbering above: 1. method call 2. method call that generates message to active object thread of control and returns 3. method call -> lightweight message on the same box or LRPC 4. method call -> external system message (tcp, udp, tib, etc) or RPC
If it can be enabled by a policy driven approach then you can change the architecture of your application by simply changing policies.
I think David Abraham's named parameters could help greatly with
approach, along with the serialization lib for marshalling support. This should provide a solid basis for this. I've also thought that perhaps boost::signals might be the correct approach to take for
this this
but I'm unsure.
To support this approach you also need mutex aspects that are also policy driven. I am preparing to submit such locking that builds on the existing mutexes and locks.
<phew> That is quite a scope. But yes, I can see that ultimately it should cover all these dimensions. Maybe a phasing?
Yep, I think the locking and mutex stuff needs to be cleaned up or wrapped to suit a policy approach. Need atomic op support too. Next step is a thread safe queue. Perhaps two, one with priority and a normal one. I've got these I can submit but they're defective in terms of exception safety. Next step is a way having a policy based interface approach to make calling a function result in the calling of a function or marshalling the parameters and calling something else... This is the bit where I'm not sure how best to approach it. Then it is a latter of adding transports to enrich the approach.
Cheers, Scott
Good thinking there Scott. Regards, Matt Hurd _______________________ Susquehanna Pacific P/L hurdm@sig.com +61.2.8226.5029 _______________________ IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

boost.org
[mailto:boost-bounces@lists.boost.org]On Behalf Of Hurd, Matthew Sent: Tuesday, February 17, 2004 4:31 PM
thanks for the numbers! excellent stuff. would like to add the note that in an "ideal" deployment of the ActiveObject pattern, it brings zero overhead. thats a wee bit sensational to make the point. but the overheads incurred with mechansims such as inter-thread messaging were there anyway. so yes copying, queueing, dequeueing and signaling a thread all consume cycles. but the application would have had to do something similar. ActiveObject is just a formalization and acknowledgement of it.
Threading is a big overhead though. Consider these numbers:
Safe increment (Athlon 2800+) (P4 3.2GHz) Interlocked primitive 11 ns 53 ns Critical section 138 ns 411 ns
<snip>
I suggest that notions of "function call" should be exorcised from discussions that are targeting active objects. That type of thinking is perfectly valid in some other thread (oops) but can scuttle work targeting active objects; the behaviour (i.e. the code) is inherently in the object.
Not sure I agree. Think of it the small talk way. A message is a method call.
hmmmm. tangle with this one all the time and only have bruises and piles of discarded source to show for it :-( yes, smalltalk was the original and it did reinforce the notion of message vs method (it has "proper" dynamic binding). there are two hidden nasties here (IMHO). first is the assumption that when we "send a message" this is directly mapped to a method. once you have active objects up and... active? the messages go through a "strange and wonderful" morphosis. they do not directly match the methods in the active object. the messages reflect the _interaction_ that is occurring. analogies can be drawn with the use case concept, where actors work through interactions with the system. active objects interact rather than make calls to each others methods. but that is probably a sad abuse of uml. secondly, sync calls (i.e. C++ methods) that internally queue messages that are eventually processed and result in calls to (other?) methods in the same C++ instance implies that the callers i/f and the servant instance are one-and-the-same. among other things this leads to a difficulty in separating servant code from the client. i'm tiring :-( i feel good about the goal but there is a point of critical mass that feels like its constantly moving away. and i had better do some "real work" for a while. hope there has been sufficient content and clarity in my messages cheers, scott

"scott" <scottw@qbik.com> writes:
Boost.org
[mailto:boost-bounces@lists.boost.org]On Behalf Of Hurd, Matthew Sent: Tuesday, February 17, 2004 4:31 PM
Thanks for the numbers!
Excellent stuff. would like to add the note that in an "ideal" deployment of the ActiveObject pattern, it brings zero overhead. Thats a wee bit sensational to make the point. but the overheads incurred with mechansims such as inter-thread messaging were there anyway. so yes copying, queueing, dequeueing and signaling a thread all consume cycles. but the application would have had to do something similar. ActiveObject is just a formalization and acknowledgement of it.
Hmm, thank goodness that emacs/gnus has a "Capitalize" key command for messages like this one. Sadly, it didn't fix everything ;-) Are ActiveObjects like all objects were in Simula: each running in its own thread, with fully asynchronous inter-object communications? -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Tue, 17 Feb 2004 21:30:08 -0500 David Abrahams <dave@boost-consulting.com> wrote:
Are ActiveObjects like all objects were in Simula: each running in its own thread, with fully asynchronous inter-object communications?
Doesn't have to be. An Active Object could actually have a number of threads doing work on its behalf. -- Jody Hagins "One lawyer can steal more than a hundred men with guns." -- The Godfather

[mailto:boost-bounces@lists.boost.org]On Behalf Of David Abrahams
Hmm, thank goodness that emacs/gnus has a "Capitalize" key command for messages like this one. Sadly, it didn't fix everything ;-)
Too much time spend in ksh perhaps.
Are ActiveObjects like all objects were in Simula: each running in its own thread, with fully asynchronous inter-object communications?
There were a few responses to your question. This is in response to those as well. I can't make comparisons with Simula as I have never used it. But the short answer to your question is "no". An ActiveObject may "equate to" (sorry, can't think of a better term) one thread or no thread. struct servant_base { virtual void operator()( signal & ) = 0; }; struct scheduler_base : public servant_base { std::deque<signal> pending; void receive( signal & ); virtual void operator()( signal & ) = 0; }; struct threaded_active_object : public scheduler_base { virtual void operator()( signal & ) { }}; struct unthreaded_active_object : public servant_base { virtual void operator()( signal & ) { } }; The scheduler_base class is the one that needs to fire up a boost::thread. The code that executes will detect inbound signals (arriving via "receive") and remove them from "pending". Then there is a "dispatch" function (all described in the pattern) that arranges for the operator() call on the proper servant_base-derived instance, passing a signal. The ActiveObject whose code is eventually executed may be a servant-derived or scheduler-derived. So, my only deviation from other responses is to do with the statement that ActiveObjects can "equate to" many threads. In my (comfortable but shrinking) world, an ActiveObject may manage subordinate ActiveObjects that are scheduler-derived and in this indirect sense "equate to" multiple threads. But a class deriving from servant_base (possibly via scheduler) is a zero-or-one- thread-class. cheers, sCOtT :-)
participants (4)
-
David Abrahams
-
Hurd, Matthew
-
Jody Hagins
-
scott